diff --git a/package.json b/package.json index f502dafc..cd4ae31d 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.6", + "@tanstack/query-core": "^5.90.10", "@tanstack/react-virtual": "3.13.12", "@tauri-apps/api": "^2.9.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", @@ -78,7 +78,7 @@ "fast-deep-equal": "^3.1.3", "file-saver": "^2.0.5", "get-text-width": "^1.0.3", - "html-react-parser": "^5.2.8", + "html-react-parser": "^5.2.10", "itertools": "^2.5.0", "js-base64": "^3.7.8", "lodash-es": "^4.17.21", @@ -97,10 +97,10 @@ "react-loading-skeleton": "^3.5.0", "react-markdown": "^10.1.0", "react-qr-code": "^2.0.18", - "react-router-dom": "^6.30.1", + "react-router-dom": "^6.30.2", "react-use-websocket": "^4.13.0", "react-virtualized-auto-sizer": "^1.0.26", - "recharts": "^3.3.0", + "recharts": "^3.4.1", "rehype-sanitize": "^6.0.0", "rxjs": "^7.8.2", "use-breakpoint": "^4.0.10", @@ -108,20 +108,20 @@ "zustand": "^5.0.8" }, "devDependencies": { - "@biomejs/biome": "^2.3.3", + "@biomejs/biome": "^2.3.5", "@hookform/devtools": "^4.4.0", "@svgr/cli": "^8.1.0", - "@tanstack/react-query": "^5.90.6", + "@tanstack/react-query": "^5.90.10", "@tanstack/react-query-devtools": "^5.90.2", - "@tauri-apps/cli": "^2.9.3", + "@tauri-apps/cli": "^2.9.4", "@types/file-saver": "^2.0.7", "@types/lodash-es": "^4.17.12", - "@types/node": "^24.10.0", - "@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", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react-swc": "^4.2.2", + "autoprefixer": "^10.4.22", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "prettier": "^3.6.2", @@ -129,7 +129,7 @@ "typedoc": "^0.28.14", "typesafe-i18n": "^5.26.2", "typescript": "^5.9.3", - "vite": "^7.1.12" + "vite": "^7.2.2" }, "volta": { "node": "20.5.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3ac7ace..975bb0ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/query-core': - specifier: ^5.90.6 - version: 5.90.6 + specifier: ^5.90.10 + version: 5.90.10 '@tanstack/react-virtual': specifier: 3.13.12 version: 3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -102,8 +102,8 @@ importers: specifier: ^1.0.3 version: 1.0.3 html-react-parser: - specifier: ^5.2.8 - version: 5.2.8(@types/react@19.2.2)(react@19.2.0) + specifier: ^5.2.10 + version: 5.2.10(@types/react@19.2.5)(react@19.2.0) itertools: specifier: ^2.5.0 version: 2.5.0 @@ -115,7 +115,7 @@ importers: version: 4.17.21 merge-refs: specifier: ^2.0.0 - version: 2.0.0(@types/react@19.2.2) + version: 2.0.0(@types/react@19.2.5) millify: specifier: ^6.1.0 version: 6.1.0 @@ -154,13 +154,13 @@ importers: version: 3.5.0(react@19.2.0) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.2)(react@19.2.0) + version: 10.1.0(@types/react@19.2.5)(react@19.2.0) react-qr-code: specifier: ^2.0.18 version: 2.0.18(react@19.2.0) react-router-dom: - specifier: ^6.30.1 - version: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^6.30.2 + version: 6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-use-websocket: specifier: ^4.13.0 version: 4.13.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.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) + specifier: ^3.4.1 + version: 3.4.1(@types/react@19.2.5)(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 @@ -184,26 +184,26 @@ importers: version: 3.25.76 zustand: specifier: ^5.0.8 - 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)) + version: 5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@biomejs/biome': - specifier: ^2.3.3 - version: 2.3.3 + specifier: ^2.3.5 + version: 2.3.5 '@hookform/devtools': specifier: ^4.4.0 - version: 4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 4.4.0(@types/react@19.2.5)(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.6 - version: 5.90.6(react@19.2.0) + specifier: ^5.90.10 + version: 5.90.10(react@19.2.0) '@tanstack/react-query-devtools': specifier: ^5.90.2 - version: 5.90.2(@tanstack/react-query@5.90.6(react@19.2.0))(react@19.2.0) + version: 5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0) '@tauri-apps/cli': - specifier: ^2.9.3 - version: 2.9.3 + specifier: ^2.9.4 + version: 2.9.4 '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -211,23 +211,23 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^24.10.0 - version: 24.10.0 + specifier: ^24.10.1 + version: 24.10.1 '@types/react': - specifier: ^19.2.2 - version: 19.2.2 + specifier: ^19.2.5 + version: 19.2.5 '@types/react-dom': - specifier: ^19.2.2 - version: 19.2.2(@types/react@19.2.2) + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.5) '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + specifier: ^5.1.1 + version: 5.1.1(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1)) '@vitejs/plugin-react-swc': - specifier: ^4.2.0 - version: 4.2.0(vite@7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + specifier: ^4.2.2 + version: 4.2.2(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1)) autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) + specifier: ^10.4.22 + version: 10.4.22(postcss@8.5.6) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -250,8 +250,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.1.12 - version: 7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + specifier: ^7.2.2 + version: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) packages: @@ -342,55 +342,55 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.3.3': - resolution: {integrity: sha512-zn/P1pRBCpDdhi+VNSMnpczOz9DnqzOA2c48K8xgxjDODvi5O8gs3a2H233rck/5HXpkFj6TmyoqVvxirZUnvg==} + '@biomejs/biome@2.3.5': + resolution: {integrity: sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.3.3': - resolution: {integrity: sha512-5+JtW6RKmjqL9un0UtHV0ezOslAyYBzyl5ZhYiu7GHesX2x8NCDl6tXYrenv9m7e1RLbkO5E5Kh04kseMtz6lw==} + '@biomejs/cli-darwin-arm64@2.3.5': + resolution: {integrity: sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.3.3': - resolution: {integrity: sha512-UPmKRalkHicvIpeccuKqq+/gA2HYV8FUnAEDJnqYBlGlycKqe6xrovWqvWTE4TTNpIFf4UQyuaDzLkN6Kz6tbA==} + '@biomejs/cli-darwin-x64@2.3.5': + resolution: {integrity: sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.3.3': - resolution: {integrity: sha512-KhCDMV+V7Yu72v40ssGJTHuv/j0n7JQ6l0s/c+EMcX5zPYLMLr4XpmI+WXhp4Vfkz0T5Xnh5wbrTBI3f2UTpjQ==} + '@biomejs/cli-linux-arm64-musl@2.3.5': + resolution: {integrity: sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.3.3': - resolution: {integrity: sha512-zeiKwALNB/hax7+LLhCYqhqzlWdTfgE9BGkX2Z8S4VmCYnGFrf2fON/ec6KCos7mra5MDm6fYICsEWN2+HKZhw==} + '@biomejs/cli-linux-arm64@2.3.5': + resolution: {integrity: sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.3.3': - resolution: {integrity: sha512-IyqQ+jYzU5MVy9CK5NV0U+NnUMPUAhYMrB/x4QgL/Dl1MqzBVc61bHeyhLnKM6DSEk73/TQYrk/8/QmVHudLdQ==} + '@biomejs/cli-linux-x64-musl@2.3.5': + resolution: {integrity: sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.3.3': - resolution: {integrity: sha512-05CjPLbvVVU8J6eaO6iSEoA0FXKy2l6ddL+1h/VpiosCmIp3HxRKLOa1hhC1n+D13Z8g9b1DtnglGtM5U3sTag==} + '@biomejs/cli-linux-x64@2.3.5': + resolution: {integrity: sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.3.3': - resolution: {integrity: sha512-NtlLs3pdFqFAQYZjlEHKOwJEn3GEaz7rtR2oCrzaLT2Xt3Cfd55/VvodQ5V+X+KepLa956QJagckJrNL+DmumQ==} + '@biomejs/cli-win32-arm64@2.3.5': + resolution: {integrity: sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.3.3': - resolution: {integrity: sha512-klJKPPQvUk9Rlp0Dd56gQw/+Wt6uUprHdHWtbDC93f3Iv+knA2tLWpcYoOZJgPV+9s+RBmYv0DGy4mUlr20esg==} + '@biomejs/cli-win32-x64@2.3.5': + resolution: {integrity: sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ==} engines: {node: '>=14.21.3'} 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.14.0': - resolution: {integrity: sha512-c5X8fwPLOtUS8TVdqhynz9iV0GlOtFUT1ppXYzUUlEXe4kbZ/mvMT8wXoT8kCwUka+zsiloq7sD3pZ3+QVTuNQ==} + '@gerrit0/mini-shiki@3.15.0': + resolution: {integrity: sha512-L5IHdZIDa4bG4yJaOzfasOH/o22MCesY0mx+n6VATbaiCtMeR59pdRqYk4bEiQkIHfxsHPNgdi7VJlZb2FhdMQ==} '@hookform/devtools@4.4.0': resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==} @@ -764,134 +764,134 @@ packages: react-redux: optional: true - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} + '@remix-run/router@1.23.1': + resolution: {integrity: sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==} engines: {node: '>=14.0.0'} - '@rolldown/pluginutils@1.0.0-beta.43': - resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rollup/rollup-android-arm-eabi@4.52.5': - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.5': - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.5': - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.5': - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.5': - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.5': - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.5': - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.5': - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.5': - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.5': - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.5': - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.5': - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.5': - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.5': - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.5': - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.14.0': - resolution: {integrity: sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==} + '@shikijs/engine-oniguruma@3.15.0': + resolution: {integrity: sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==} - '@shikijs/langs@3.14.0': - resolution: {integrity: sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==} + '@shikijs/langs@3.15.0': + resolution: {integrity: sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==} - '@shikijs/themes@3.14.0': - resolution: {integrity: sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==} + '@shikijs/themes@3.15.0': + resolution: {integrity: sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==} - '@shikijs/types@3.14.0': - resolution: {integrity: sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==} + '@shikijs/types@3.15.0': + resolution: {integrity: sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -1011,68 +1011,68 @@ packages: peerDependencies: '@svgr/core': '*' - '@swc/core-darwin-arm64@1.15.0': - resolution: {integrity: sha512-TBKWkbnShnEjlIbO4/gfsrIgAqHBVqgPWLbWmPdZ80bF393yJcLgkrb7bZEnJs6FCbSSuGwZv2rx1jDR2zo6YA==} + '@swc/core-darwin-arm64@1.15.2': + resolution: {integrity: sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.0': - resolution: {integrity: sha512-f5JKL1v1H56CIZc1pVn4RGPOfnWqPwmuHdpf4wesvXunF1Bx85YgcspW5YxwqG5J9g3nPU610UFuExJXVUzOiQ==} + '@swc/core-darwin-x64@1.15.2': + resolution: {integrity: sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.0': - resolution: {integrity: sha512-duK6nG+WyuunnfsfiTUQdzC9Fk8cyDLqT9zyXvY2i2YgDu5+BH5W6wM5O4mDNCU5MocyB/SuF5YDF7XySnowiQ==} + '@swc/core-linux-arm-gnueabihf@1.15.2': + resolution: {integrity: sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.0': - resolution: {integrity: sha512-ITe9iDtTRXM98B91rvyPP6qDVbhUBnmA/j4UxrHlMQ0RlwpqTjfZYZkD0uclOxSZ6qIrOj/X5CaoJlDUuQ0+Cw==} + '@swc/core-linux-arm64-gnu@1.15.2': + resolution: {integrity: sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.15.0': - resolution: {integrity: sha512-Q5ldc2bzriuzYEoAuqJ9Vr3FyZhakk5hiwDbniZ8tlEXpbjBhbOleGf9/gkhLaouDnkNUEazFW9mtqwUTRdh7Q==} + '@swc/core-linux-arm64-musl@1.15.2': + resolution: {integrity: sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.15.0': - resolution: {integrity: sha512-pY4is+jEpOxlYCSnI+7N8Oxbap9TmTz5YT84tUvRTlOlTBwFAUlWFCX0FRwWJlsfP0TxbqhIe8dNNzlsEmJbXQ==} + '@swc/core-linux-x64-gnu@1.15.2': + resolution: {integrity: sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.15.0': - resolution: {integrity: sha512-zYEt5eT8y8RUpoe7t5pjpoOdGu+/gSTExj8PV86efhj6ugB3bPlj3Y85ogdW3WMVXr4NvwqvzdaYGCZfXzSyVg==} + '@swc/core-linux-x64-musl@1.15.2': + resolution: {integrity: sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.15.0': - resolution: {integrity: sha512-zC1rmOgFH5v2BCbByOazEqs0aRNpTdLRchDExfcCfgKgeaD+IdpUOqp7i3VG1YzkcnbuZjMlXfM0ugpt+CddoA==} + '@swc/core-win32-arm64-msvc@1.15.2': + resolution: {integrity: sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.0': - resolution: {integrity: sha512-7t9U9KwMwQblkdJIH+zX1V4q1o3o41i0HNO+VlnAHT5o+5qHJ963PHKJ/pX3P2UlZnBCY465orJuflAN4rAP9A==} + '@swc/core-win32-ia32-msvc@1.15.2': + resolution: {integrity: sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.0': - resolution: {integrity: sha512-VE0Zod5vcs8iMLT64m5QS1DlTMXJFI/qSgtMDRx8rtZrnjt6/9NW8XUaiPJuRu8GluEO1hmHoyf1qlbY19gGSQ==} + '@swc/core-win32-x64-msvc@1.15.2': + resolution: {integrity: sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.0': - resolution: {integrity: sha512-8SnJV+JV0rYbfSiEiUvYOmf62E7QwsEG+aZueqSlKoxFt0pw333+bgZSQXGUV6etXU88nxur0afVMaINujBMSw==} + '@swc/core@1.15.2': + resolution: {integrity: sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -1086,8 +1086,8 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tanstack/query-core@5.90.6': - resolution: {integrity: sha512-AnZSLF26R8uX+tqb/ivdrwbVdGemdEDm1Q19qM6pry6eOZ6bEYiY7mWhzXT1YDIPTNEVcZ5kYP9nWjoxDLiIVw==} + '@tanstack/query-core@5.90.10': + resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==} '@tanstack/query-devtools@5.90.1': resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} @@ -1098,8 +1098,8 @@ packages: '@tanstack/react-query': ^5.90.2 react: ^18 || ^19 - '@tanstack/react-query@5.90.6': - resolution: {integrity: sha512-gB1sljYjcobZKxjPbKSa31FUTyr+ROaBdoH+wSSs9Dk+yDCmMs+TkTV3PybRRVLC7ax7q0erJ9LvRWnMktnRAw==} + '@tanstack/react-query@5.90.10': + resolution: {integrity: sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==} peerDependencies: react: ^18 || ^19 @@ -1115,74 +1115,74 @@ packages: '@tauri-apps/api@2.9.0': resolution: {integrity: sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==} - '@tauri-apps/cli-darwin-arm64@2.9.3': - resolution: {integrity: sha512-W8FQXZXQmQ0Fmj9UJXNrm2mLdIaLLriKVY7o/FzmizyIKTPIvHjfZALTNybbpTQRbJvKoGHLrW1DNzAWVDWJYg==} + '@tauri-apps/cli-darwin-arm64@2.9.4': + resolution: {integrity: sha512-9rHkMVtbMhe0AliVbrGpzMahOBg3rwV46JYRELxR9SN6iu1dvPOaMaiC4cP6M/aD1424ziXnnMdYU06RAH8oIw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.9.3': - resolution: {integrity: sha512-zDwu40rlshijt3TU6aRvzPUyVpapsx1sNfOlreDMTaMelQLHl6YoQzSRpLHYwrHrhimxyX2uDqnKIiuGel0Lhg==} + '@tauri-apps/cli-darwin-x64@2.9.4': + resolution: {integrity: sha512-VT9ymNuT06f5TLjCZW2hfSxbVtZDhORk7CDUDYiq5TiSYQdxkl8MVBy0CCFFcOk4QAkUmqmVUA9r3YZ/N/vPRQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.3': - resolution: {integrity: sha512-+Oc2OfcTRwYtW93VJqd/HOk77buORwC9IToj/qsEvM7bTMq6Kda4alpZprzwrCHYANSw+zD8PgjJdljTpe4p+g==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.9.4': + resolution: {integrity: sha512-tTWkEPig+2z3Rk0zqZYfjUYcgD+aSm72wdrIhdYobxbQZOBw0zfn50YtWv+av7bm0SHvv75f0l7JuwgZM1HFow==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.9.3': - resolution: {integrity: sha512-59GqU/J1n9wFyAtleoQOaU0oVIo+kwQynEw4meFDoKRXszKGor6lTsbsS3r0QKLSPbc0o/yYGJhqqCtkYjb/eg==} + '@tauri-apps/cli-linux-arm64-gnu@2.9.4': + resolution: {integrity: sha512-ql6vJ611qoqRYHxkKPnb2vHa27U+YRKRmIpLMMBeZnfFtZ938eao7402AQCH1mO2+/8ioUhbpy9R/ZcLTXVmkg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-arm64-musl@2.9.3': - resolution: {integrity: sha512-fzvG+jEn5/iYGNH6Z2IRMheYFC4pJdXa19BR9fFm6Bdn2cuajRLDKdUcEME/DCtwqclphXtFZTrT4oezY5vI/A==} + '@tauri-apps/cli-linux-arm64-musl@2.9.4': + resolution: {integrity: sha512-vg7yNn7ICTi6hRrcA/6ff2UpZQP7un3xe3SEld5QM0prgridbKAiXGaCKr3BnUBx/rGXegQlD/wiLcWdiiraSw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-riscv64-gnu@2.9.3': - resolution: {integrity: sha512-qV8DZXI/fZwawk6T3Th1g6smiNC2KeQTk7XFgKvqZ6btC01z3UTsQmNGvI602zwm3Ld1TBZb4+rEWu2QmQimmw==} + '@tauri-apps/cli-linux-riscv64-gnu@2.9.4': + resolution: {integrity: sha512-l8L+3VxNk6yv5T/Z/gv5ysngmIpsai40B9p6NQQyqYqxImqYX37pqREoEBl1YwG7szGnDibpWhidPrWKR59OJA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@tauri-apps/cli-linux-x64-gnu@2.9.3': - resolution: {integrity: sha512-tquyEONCNRfqEBWEe4eAHnxFN5yY5lFkCuD4w79XLIovUxVftQ684+xLp7zkhntkt4y20SMj2AgJa/+MOlx4Kg==} + '@tauri-apps/cli-linux-x64-gnu@2.9.4': + resolution: {integrity: sha512-PepPhCXc/xVvE3foykNho46OmCyx47E/aG676vKTVp+mqin5d+IBqDL6wDKiGNT5OTTxKEyNlCQ81Xs2BQhhqA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-linux-x64-musl@2.9.3': - resolution: {integrity: sha512-v2cBIB/6ji8DL+aiL5QUykU3ZO8OoJGyx50/qv2HQVzkf85KdaYSis3D/oVRemN/pcDz+vyCnnL3XnzFnDl4JQ==} + '@tauri-apps/cli-linux-x64-musl@2.9.4': + resolution: {integrity: sha512-zcd1QVffh5tZs1u1SCKUV/V7RRynebgYUNWHuV0FsIF1MjnULUChEXhAhug7usCDq4GZReMJOoXa6rukEozWIw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-win32-arm64-msvc@2.9.3': - resolution: {integrity: sha512-ZGvBy7nvrHPbE0HeKp/ioaiw8bNgAHxWnb7JRZ4/G0A+oFj0SeSFxl9k5uU6FKnM7bHM23Gd1oeaDex9g5Fceg==} + '@tauri-apps/cli-win32-arm64-msvc@2.9.4': + resolution: {integrity: sha512-/7ZhnP6PY04bEob23q8MH/EoDISdmR1wuNm0k9d5HV7TDMd2GGCDa8dPXA4vJuglJKXIfXqxFmZ4L+J+MO42+w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.9.3': - resolution: {integrity: sha512-UsgIwOnpCoY9NK9/65QiwgmWVIE80LE7SwRYVblGtmlY9RYfsYvpbItwsovA/AcHMTiO+OCvS/q9yLeqS3m6Sg==} + '@tauri-apps/cli-win32-ia32-msvc@2.9.4': + resolution: {integrity: sha512-1LmAfaC4Cq+3O1Ir1ksdhczhdtFSTIV51tbAGtbV/mr348O+M52A/xwCCXQank0OcdBxy5BctqkMtuZnQvA8uQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.9.3': - resolution: {integrity: sha512-fmw7NrrHE5m49idCvJAx9T9bsupjdJ0a3p3DPCNCZRGANU6R1tA1L+KTlVuUtdAldX2NqU/9UPo2SCslYKgJHQ==} + '@tauri-apps/cli-win32-x64-msvc@2.9.4': + resolution: {integrity: sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.9.3': - resolution: {integrity: sha512-BQ7iLUXTQcyG1PpzLWeVSmBCedYDpnA/6Cm/kRFGtqjTf/eVUlyYO5S2ee07tLum3nWwDBWTGFZeruO8yEukfA==} + '@tauri-apps/cli@2.9.4': + resolution: {integrity: sha512-pvylWC9QckrOS9ATWXIXcgu7g2hKK5xTL5ZQyZU/U0n9l88SEFGcWgLQNa8WZmd+wWIOWhkxOFcOl3i6ubDNNw==} engines: {node: '>= 10'} hasBin: true @@ -1292,19 +1292,19 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.10.0': - resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/react-dom@19.2.2': - resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.2': - resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/react@19.2.5': + resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1326,14 +1326,14 @@ packages: peerDependencies: react: '>= 16.8.0' - '@vitejs/plugin-react-swc@4.2.0': - resolution: {integrity: sha512-/tesahXD1qpkGC6FzMoFOJj0RyZdw9xLELOL+6jbElwmWfwOnIVy+IfpY+o9JfD9PKaR/Eyb6DNrvbXpuvA+8Q==} + '@vitejs/plugin-react-swc@4.2.2': + resolution: {integrity: sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 - '@vitejs/plugin-react@5.1.0': - resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==} + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1365,8 +1365,8 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.4.22: + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -1386,8 +1386,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.24: - resolution: {integrity: sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==} + baseline-browser-mapping@2.8.28: + resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} hasBin: true boolbase@1.0.0: @@ -1403,8 +1403,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.27.0: - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1437,8 +1437,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001753: - resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==} + caniuse-lite@1.0.30001755: + resolution: {integrity: sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1550,8 +1550,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.2: + resolution: {integrity: sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==} d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} @@ -1682,8 +1682,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.244: - resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==} + electron-to-chromium@1.5.254: + resolution: {integrity: sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1723,8 +1723,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es-toolkit@1.41.0: - resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==} + es-toolkit@1.42.0: + resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} @@ -1778,8 +1778,8 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} framer-motion@12.23.24: resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==} @@ -1902,11 +1902,11 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - html-dom-parser@5.1.1: - resolution: {integrity: sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==} + html-dom-parser@5.1.2: + resolution: {integrity: sha512-9nD3Rj3/FuQt83AgIa1Y3ruzspwFFA54AJbQnohXN+K6fL1/bhcDQJJY5Ne4L4A163ADQFVESd/0TLyNoV0mfg==} - html-react-parser@5.2.8: - resolution: {integrity: sha512-09WaI81tbpwhXWeMe1m9VptZVJUcigo0l59zVt+2HUIQT7+baU38/oNhllj6MKhOuGXqh0nrlwOgxbxbm6xXHw==} + html-react-parser@5.2.10: + resolution: {integrity: sha512-DjOLloguuDA+Ed7Q7PKhvMQmCl2+Yk/pfvvca68fvn15QFBbL4uHGxXwoXQ4sqS0UyuRH2lJb0S8yZCL3lvehQ==} peerDependencies: '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 react: 0.14 || 15 || 16 || 17 || 18 || 19 @@ -1937,8 +1937,8 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inline-style-parser@0.2.6: - resolution: {integrity: sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} @@ -2086,8 +2086,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: @@ -2518,15 +2518,15 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} + react-router-dom@6.30.2: + resolution: {integrity: sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} + react-router@6.30.2: + resolution: {integrity: sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -2557,8 +2557,8 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - recharts@3.3.0: - resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} + recharts@3.4.1: + resolution: {integrity: sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2606,8 +2606,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2743,11 +2743,11 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - style-to-js@1.1.19: - resolution: {integrity: sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - style-to-object@1.0.12: - resolution: {integrity: sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw==} + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -2896,8 +2896,8 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite@7.1.12: - resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3053,7 +3053,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.27.0 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -3127,39 +3127,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.3.3': + '@biomejs/biome@2.3.5': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.3 - '@biomejs/cli-darwin-x64': 2.3.3 - '@biomejs/cli-linux-arm64': 2.3.3 - '@biomejs/cli-linux-arm64-musl': 2.3.3 - '@biomejs/cli-linux-x64': 2.3.3 - '@biomejs/cli-linux-x64-musl': 2.3.3 - '@biomejs/cli-win32-arm64': 2.3.3 - '@biomejs/cli-win32-x64': 2.3.3 - - '@biomejs/cli-darwin-arm64@2.3.3': + '@biomejs/cli-darwin-arm64': 2.3.5 + '@biomejs/cli-darwin-x64': 2.3.5 + '@biomejs/cli-linux-arm64': 2.3.5 + '@biomejs/cli-linux-arm64-musl': 2.3.5 + '@biomejs/cli-linux-x64': 2.3.5 + '@biomejs/cli-linux-x64-musl': 2.3.5 + '@biomejs/cli-win32-arm64': 2.3.5 + '@biomejs/cli-win32-x64': 2.3.5 + + '@biomejs/cli-darwin-arm64@2.3.5': optional: true - '@biomejs/cli-darwin-x64@2.3.3': + '@biomejs/cli-darwin-x64@2.3.5': optional: true - '@biomejs/cli-linux-arm64-musl@2.3.3': + '@biomejs/cli-linux-arm64-musl@2.3.5': optional: true - '@biomejs/cli-linux-arm64@2.3.3': + '@biomejs/cli-linux-arm64@2.3.5': optional: true - '@biomejs/cli-linux-x64-musl@2.3.3': + '@biomejs/cli-linux-x64-musl@2.3.5': optional: true - '@biomejs/cli-linux-x64@2.3.3': + '@biomejs/cli-linux-x64@2.3.5': optional: true - '@biomejs/cli-win32-arm64@2.3.3': + '@biomejs/cli-win32-arm64@2.3.5': optional: true - '@biomejs/cli-win32-x64@2.3.3': + '@biomejs/cli-win32-x64@2.3.5': optional: true '@emotion/babel-plugin@11.13.5': @@ -3194,7 +3194,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)': + '@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 @@ -3206,7 +3206,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 transitivePeerDependencies: - supports-color @@ -3216,22 +3216,22 @@ snapshots: '@emotion/memoize': 0.9.0 '@emotion/unitless': 0.10.0 '@emotion/utils': 1.4.2 - csstype: 3.1.3 + csstype: 3.2.2 '@emotion/sheet@1.4.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)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(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.2)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.5)(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.2 + '@types/react': 19.2.5 transitivePeerDependencies: - supports-color @@ -3348,18 +3348,18 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@gerrit0/mini-shiki@3.14.0': + '@gerrit0/mini-shiki@3.15.0': dependencies: - '@shikijs/engine-oniguruma': 3.14.0 - '@shikijs/langs': 3.14.0 - '@shikijs/themes': 3.14.0 - '@shikijs/types': 3.14.0 + '@shikijs/engine-oniguruma': 3.15.0 + '@shikijs/langs': 3.15.0 + '@shikijs/themes': 3.15.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@hookform/devtools@4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@hookform/devtools@4.4.0(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@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) + '@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0) '@types/lodash': 4.17.20 little-state-machine: 4.8.1(react@19.2.0) lodash: 4.17.21 @@ -3470,7 +3470,7 @@ snapshots: '@react-hook/passive-layout-effect': 1.2.1(react@19.2.0) react: 19.2.0 - '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': + '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.5)(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 @@ -3480,92 +3480,92 @@ snapshots: reselect: 5.1.1 optionalDependencies: react: 19.2.0 - react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1) - '@remix-run/router@1.23.0': {} + '@remix-run/router@1.23.1': {} - '@rolldown/pluginutils@1.0.0-beta.43': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rollup/rollup-android-arm-eabi@4.52.5': + '@rollup/rollup-android-arm-eabi@4.53.2': optional: true - '@rollup/rollup-android-arm64@4.52.5': + '@rollup/rollup-android-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-arm64@4.52.5': + '@rollup/rollup-darwin-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-x64@4.52.5': + '@rollup/rollup-darwin-x64@4.53.2': optional: true - '@rollup/rollup-freebsd-arm64@4.52.5': + '@rollup/rollup-freebsd-arm64@4.53.2': optional: true - '@rollup/rollup-freebsd-x64@4.52.5': + '@rollup/rollup-freebsd-x64@4.53.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.5': + '@rollup/rollup-linux-arm-musleabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.5': + '@rollup/rollup-linux-arm64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': + '@rollup/rollup-linux-arm64-musl@4.53.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.5': + '@rollup/rollup-linux-loong64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': + '@rollup/rollup-linux-ppc64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': + '@rollup/rollup-linux-riscv64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': + '@rollup/rollup-linux-riscv64-musl@4.53.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': + '@rollup/rollup-linux-s390x-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': + '@rollup/rollup-linux-x64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': + '@rollup/rollup-linux-x64-musl@4.53.2': optional: true - '@rollup/rollup-openharmony-arm64@4.52.5': + '@rollup/rollup-openharmony-arm64@4.53.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': + '@rollup/rollup-win32-arm64-msvc@4.53.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': + '@rollup/rollup-win32-ia32-msvc@4.53.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.5': + '@rollup/rollup-win32-x64-gnu@4.53.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': + '@rollup/rollup-win32-x64-msvc@4.53.2': optional: true - '@shikijs/engine-oniguruma@3.14.0': + '@shikijs/engine-oniguruma@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.14.0': + '@shikijs/langs@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 - '@shikijs/themes@3.14.0': + '@shikijs/themes@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 - '@shikijs/types@3.14.0': + '@shikijs/types@3.15.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -3704,51 +3704,51 @@ snapshots: transitivePeerDependencies: - typescript - '@swc/core-darwin-arm64@1.15.0': + '@swc/core-darwin-arm64@1.15.2': optional: true - '@swc/core-darwin-x64@1.15.0': + '@swc/core-darwin-x64@1.15.2': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.0': + '@swc/core-linux-arm-gnueabihf@1.15.2': optional: true - '@swc/core-linux-arm64-gnu@1.15.0': + '@swc/core-linux-arm64-gnu@1.15.2': optional: true - '@swc/core-linux-arm64-musl@1.15.0': + '@swc/core-linux-arm64-musl@1.15.2': optional: true - '@swc/core-linux-x64-gnu@1.15.0': + '@swc/core-linux-x64-gnu@1.15.2': optional: true - '@swc/core-linux-x64-musl@1.15.0': + '@swc/core-linux-x64-musl@1.15.2': optional: true - '@swc/core-win32-arm64-msvc@1.15.0': + '@swc/core-win32-arm64-msvc@1.15.2': optional: true - '@swc/core-win32-ia32-msvc@1.15.0': + '@swc/core-win32-ia32-msvc@1.15.2': optional: true - '@swc/core-win32-x64-msvc@1.15.0': + '@swc/core-win32-x64-msvc@1.15.2': optional: true - '@swc/core@1.15.0': + '@swc/core@1.15.2': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.0 - '@swc/core-darwin-x64': 1.15.0 - '@swc/core-linux-arm-gnueabihf': 1.15.0 - '@swc/core-linux-arm64-gnu': 1.15.0 - '@swc/core-linux-arm64-musl': 1.15.0 - '@swc/core-linux-x64-gnu': 1.15.0 - '@swc/core-linux-x64-musl': 1.15.0 - '@swc/core-win32-arm64-msvc': 1.15.0 - '@swc/core-win32-ia32-msvc': 1.15.0 - '@swc/core-win32-x64-msvc': 1.15.0 + '@swc/core-darwin-arm64': 1.15.2 + '@swc/core-darwin-x64': 1.15.2 + '@swc/core-linux-arm-gnueabihf': 1.15.2 + '@swc/core-linux-arm64-gnu': 1.15.2 + '@swc/core-linux-arm64-musl': 1.15.2 + '@swc/core-linux-x64-gnu': 1.15.2 + '@swc/core-linux-x64-musl': 1.15.2 + '@swc/core-win32-arm64-msvc': 1.15.2 + '@swc/core-win32-ia32-msvc': 1.15.2 + '@swc/core-win32-x64-msvc': 1.15.2 '@swc/counter@0.1.3': {} @@ -3756,19 +3756,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.90.6': {} + '@tanstack/query-core@5.90.10': {} '@tanstack/query-devtools@5.90.1': {} - '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.6(react@19.2.0))(react@19.2.0)': + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/query-devtools': 5.90.1 - '@tanstack/react-query': 5.90.6(react@19.2.0) + '@tanstack/react-query': 5.90.10(react@19.2.0) react: 19.2.0 - '@tanstack/react-query@5.90.6(react@19.2.0)': + '@tanstack/react-query@5.90.10(react@19.2.0)': dependencies: - '@tanstack/query-core': 5.90.6 + '@tanstack/query-core': 5.90.10 react: 19.2.0 '@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -3781,52 +3781,52 @@ snapshots: '@tauri-apps/api@2.9.0': {} - '@tauri-apps/cli-darwin-arm64@2.9.3': + '@tauri-apps/cli-darwin-arm64@2.9.4': optional: true - '@tauri-apps/cli-darwin-x64@2.9.3': + '@tauri-apps/cli-darwin-x64@2.9.4': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.3': + '@tauri-apps/cli-linux-arm-gnueabihf@2.9.4': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.9.3': + '@tauri-apps/cli-linux-arm64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.9.3': + '@tauri-apps/cli-linux-arm64-musl@2.9.4': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.9.3': + '@tauri-apps/cli-linux-riscv64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.9.3': + '@tauri-apps/cli-linux-x64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-x64-musl@2.9.3': + '@tauri-apps/cli-linux-x64-musl@2.9.4': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.9.3': + '@tauri-apps/cli-win32-arm64-msvc@2.9.4': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.9.3': + '@tauri-apps/cli-win32-ia32-msvc@2.9.4': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.9.3': + '@tauri-apps/cli-win32-x64-msvc@2.9.4': optional: true - '@tauri-apps/cli@2.9.3': + '@tauri-apps/cli@2.9.4': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.9.3 - '@tauri-apps/cli-darwin-x64': 2.9.3 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.3 - '@tauri-apps/cli-linux-arm64-gnu': 2.9.3 - '@tauri-apps/cli-linux-arm64-musl': 2.9.3 - '@tauri-apps/cli-linux-riscv64-gnu': 2.9.3 - '@tauri-apps/cli-linux-x64-gnu': 2.9.3 - '@tauri-apps/cli-linux-x64-musl': 2.9.3 - '@tauri-apps/cli-win32-arm64-msvc': 2.9.3 - '@tauri-apps/cli-win32-ia32-msvc': 2.9.3 - '@tauri-apps/cli-win32-x64-msvc': 2.9.3 + '@tauri-apps/cli-darwin-arm64': 2.9.4 + '@tauri-apps/cli-darwin-x64': 2.9.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.9.4 + '@tauri-apps/cli-linux-arm64-musl': 2.9.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.9.4 + '@tauri-apps/cli-linux-x64-gnu': 2.9.4 + '@tauri-apps/cli-linux-x64-musl': 2.9.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.9.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.9.4 + '@tauri-apps/cli-win32-x64-msvc': 2.9.4 '@tauri-apps/plugin-clipboard-manager@2.3.2': dependencies: @@ -3949,19 +3949,19 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.10.0': + '@types/node@24.10.1': dependencies: undici-types: 7.16.0 '@types/parse-json@4.0.2': {} - '@types/react-dom@19.2.2(@types/react@19.2.2)': + '@types/react-dom@19.2.3(@types/react@19.2.5)': dependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 - '@types/react@19.2.2': + '@types/react@19.2.5': dependencies: - csstype: 3.1.3 + csstype: 3.2.2 '@types/unist@2.0.11': {} @@ -3978,23 +3978,23 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.0 - '@vitejs/plugin-react-swc@4.2.0(vite@7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react-swc@4.2.2(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.43 - '@swc/core': 1.15.0 - vite: 7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@swc/core': 1.15.2 + vite: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@5.1.0(vite@7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react@5.1.1(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1))': dependencies: '@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 + '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -4027,11 +4027,11 @@ snapshots: async-function@1.0.0: {} - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.4.22(postcss@8.5.6): dependencies: - browserslist: 4.27.0 - caniuse-lite: 1.0.30001753 - fraction.js: 4.3.7 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001755 + fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 postcss: 8.5.6 @@ -4051,7 +4051,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.24: {} + baseline-browser-mapping@2.8.28: {} boolbase@1.0.0: {} @@ -4069,13 +4069,13 @@ snapshots: fill-range: 7.1.1 optional: true - browserslist@4.27.0: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.24 - caniuse-lite: 1.0.30001753 - electron-to-chromium: 1.5.244 + baseline-browser-mapping: 2.8.28 + caniuse-lite: 1.0.30001755 + electron-to-chromium: 1.5.254 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.27.0) + update-browserslist-db: 1.1.4(browserslist@4.28.0) byte-size@9.0.1: {} @@ -4100,7 +4100,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001753: {} + caniuse-lite@1.0.30001755: {} ccount@2.0.1: {} @@ -4174,7 +4174,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -4212,7 +4212,7 @@ snapshots: dependencies: css-tree: 2.2.1 - csstype@3.1.3: {} + csstype@3.2.2: {} d3-array@3.2.4: dependencies: @@ -4340,7 +4340,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.244: {} + electron-to-chromium@1.5.254: {} emoji-regex@8.0.0: {} @@ -4430,7 +4430,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.41.0: {} + es-toolkit@1.42.0: {} esbuild@0.25.12: optionalDependencies: @@ -4492,7 +4492,7 @@ snapshots: dependencies: is-callable: 1.2.7 - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} 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: @@ -4615,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.19 + style-to-js: 1.1.21 unist-util-position: 5.0.0 vfile-message: 4.0.3 transitivePeerDependencies: @@ -4631,20 +4631,20 @@ snapshots: hosted-git-info@2.8.9: {} - html-dom-parser@5.1.1: + html-dom-parser@5.1.2: dependencies: domhandler: 5.0.3 htmlparser2: 10.0.0 - html-react-parser@5.2.8(@types/react@19.2.2)(react@19.2.0): + html-react-parser@5.2.10(@types/react@19.2.5)(react@19.2.0): dependencies: domhandler: 5.0.3 - html-dom-parser: 5.1.1 + html-dom-parser: 5.1.2 react: 19.2.0 react-property: 2.0.2 - style-to-js: 1.1.19 + style-to-js: 1.1.21 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 html-url-attributes@3.0.1: {} @@ -4671,7 +4671,7 @@ snapshots: inherits@2.0.4: {} - inline-style-parser@0.2.6: {} + inline-style-parser@0.2.7: {} internal-slot@1.1.0: dependencies: @@ -4819,7 +4819,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -4976,9 +4976,9 @@ snapshots: memorystream@0.3.1: {} - merge-refs@2.0.0(@types/react@19.2.2): + merge-refs@2.0.0(@types/react@19.2.5): optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 micromark-core-commonmark@2.0.3: dependencies: @@ -5324,11 +5324,11 @@ snapshots: dependencies: react: 19.2.0 - react-markdown@10.1.0(@types/react@19.2.2)(react@19.2.0): + react-markdown@10.1.0(@types/react@19.2.5)(react@19.2.0): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.2.2 + '@types/react': 19.2.5 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -5350,27 +5350,27 @@ snapshots: qr.js: 0.0.0 react: 19.2.0 - react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.5)(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.2 + '@types/react': 19.2.5 redux: 5.0.1 react-refresh@0.18.0: {} - react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router-dom@6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-router: 6.30.1(react@19.2.0) + react-router: 6.30.2(react@19.2.0) - react-router@6.30.1(react@19.2.0): + react-router@6.30.2(react@19.2.0): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 19.2.0 react-simple-animate@3.5.3(react-dom@19.2.0(react@19.2.0)): @@ -5394,18 +5394,18 @@ snapshots: readdirp@4.1.2: {} - 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): + recharts@3.4.1(@types/react@19.2.5)(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.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.5)(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.41.0 + es-toolkit: 1.42.0 eventemitter3: 5.0.1 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.2)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.5)(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) @@ -5474,32 +5474,32 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.52.5: + rollup@4.53.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@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 + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 fsevents: 2.3.3 rxjs@7.8.2: @@ -5674,13 +5674,13 @@ snapshots: strip-bom@3.0.0: {} - style-to-js@1.1.19: + style-to-js@1.1.21: dependencies: - style-to-object: 1.0.12 + style-to-object: 1.0.14 - style-to-object@1.0.12: + style-to-object@1.0.14: dependencies: - inline-style-parser: 0.2.6 + inline-style-parser: 0.2.7 stylis@4.2.0: {} @@ -5761,7 +5761,7 @@ snapshots: typedoc@0.28.14(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.14.0 + '@gerrit0/mini-shiki': 3.15.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 @@ -5818,9 +5818,9 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - update-browserslist-db@1.1.4(browserslist@4.27.0): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -5873,16 +5873,16 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@7.1.12(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1): + vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.0 + '@types/node': 24.10.1 fsevents: 2.3.3 sass: 1.92.1 yaml: 2.8.1 @@ -5962,9 +5962,9 @@ snapshots: zod@3.25.76: {} - 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)): + zustand@5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 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 9a42820e..3d45e8d1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -117,22 +117,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -234,7 +234,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -390,7 +390,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -456,7 +456,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -473,7 +473,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -522,9 +522,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ "axum-core", "bytes", @@ -669,7 +669,7 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" -source = "git+https://github.com/DefGuard/wireguard-rs?rev=c99c0b209b19d9ce82e0f5d0d727261f8f9916b3#c99c0b209b19d9ce82e0f5d0d727261f8f9916b3" +source = "git+https://github.com/DefGuard/wireguard-rs?rev=9af5e79f60fbcf8ffa1460364d719cb1eec9f360#9af5e79f60fbcf8ffa1460364d719cb1eec9f360" dependencies = [ "aead", "base64 0.22.1", @@ -711,7 +711,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -794,9 +794,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.44" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -999,7 +999,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1225,9 +1225,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1258,7 +1258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1268,7 +1268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1295,7 +1295,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1343,7 +1343,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1357,7 +1357,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1368,7 +1368,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1379,7 +1379,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1395,6 +1395,7 @@ dependencies = [ "anyhow", "async-stream", "base64 0.22.1", + "block2 0.6.2", "chrono", "clap", "common", @@ -1406,6 +1407,9 @@ dependencies = [ "known-folders", "log", "nix", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-network-extension", "os_info", "prost", "regex", @@ -1478,7 +1482,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" version = "0.9.0" -source = "git+https://github.com/DefGuard/wireguard-rs?rev=c99c0b209b19d9ce82e0f5d0d727261f8f9916b3#c99c0b209b19d9ce82e0f5d0d727261f8f9916b3" +source = "git+https://github.com/DefGuard/wireguard-rs?rev=9af5e79f60fbcf8ffa1460364d719cb1eec9f360#9af5e79f60fbcf8ffa1460364d719cb1eec9f360" dependencies = [ "base64 0.22.1", "boringtun", @@ -1538,7 +1542,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1548,7 +1552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1561,7 +1565,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1644,7 +1648,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1676,7 +1680,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1807,7 +1811,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1828,9 +1832,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" dependencies = [ "serde", "serde_core", @@ -1914,7 +1918,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -1953,15 +1957,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" - -[[package]] -name = "fixedbitset" -version = "0.4.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" @@ -2029,7 +2027,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -2143,7 +2141,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -2285,9 +2283,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2420,7 +2418,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -2522,7 +2520,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -2707,9 +2705,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -2776,9 +2774,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", @@ -2797,7 +2795,7 @@ dependencies = [ "tokio", "tower-service", "tracing", - "windows-registry", + "windows-registry 0.6.1", ] [[package]] @@ -2944,9 +2942,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -3304,9 +3302,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -3403,7 +3401,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -3668,6 +3666,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "notify-rust" version = "4.11.7" @@ -3693,9 +3700,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", @@ -3762,7 +3769,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -3877,6 +3884,16 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -3975,6 +3992,20 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-network-extension" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9126ecdaea052ce2a6b7a894303d0806368b92c77165052da1c3eb52d9e5f9b1" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "libc", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-security", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3996,6 +4027,7 @@ checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", + "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -4017,8 +4049,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -4070,9 +4121,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -4091,7 +4142,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -4111,9 +4162,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -4150,14 +4201,18 @@ dependencies = [ [[package]] name = "os_info" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +checksum = "7c39b5918402d564846d5aba164c09a66cc88d232179dfd3e3c619a25a268392" dependencies = [ + "android_system_properties", "log", - "plist", + "nix", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-ui-kit", "serde", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4259,21 +4314,22 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "fixedbitset 0.4.2", + "fixedbitset", "indexmap 2.12.0", ] [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "fixedbitset 0.5.7", + "fixedbitset", + "hashbrown 0.15.5", "indexmap 2.12.0", ] @@ -4381,7 +4437,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -4428,7 +4484,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -4495,7 +4551,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.12.0", - "quick-xml 0.38.3", + "quick-xml 0.38.4", "serde", "time", ] @@ -4588,7 +4644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -4687,7 +4743,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.108", + "syn 2.0.110", "tempfile", ] @@ -4701,7 +4757,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -4762,9 +4818,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.0.0" +version = "21.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75" +checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" dependencies = [ "pulldown-cmark", ] @@ -4795,9 +4851,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] @@ -4859,9 +4915,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -5042,7 +5098,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5202,9 +5258,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -5395,9 +5451,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", @@ -5414,7 +5470,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5446,7 +5502,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5558,7 +5614,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5569,7 +5625,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5593,7 +5649,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5628,9 +5684,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "base64 0.22.1", "chrono", @@ -5638,7 +5694,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.0", "schemars 0.9.0", - "schemars 1.0.5", + "schemars 1.1.0", "serde_core", "serde_json", "serde_with_macros", @@ -5647,14 +5703,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5676,7 +5732,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5926,7 +5982,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -5949,7 +6005,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.108", + "syn 2.0.110", "tokio", "url", ] @@ -6134,7 +6190,7 @@ checksum = "68c6387c1c7b53060605101b63d93edca618c6cf7ce61839f2ec2a527419fdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6155,7 +6211,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6188,9 +6244,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.108" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -6214,7 +6270,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6308,7 +6364,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6325,9 +6381,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5" +checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8" dependencies = [ "anyhow", "bytes", @@ -6377,9 +6433,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38" +checksum = "87d6f8cafe6a75514ce5333f115b7b1866e8e68d9672bf4ca89fc0f35697ea9d" dependencies = [ "anyhow", "cargo_toml", @@ -6399,9 +6455,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190" +checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f" dependencies = [ "base64 0.22.1", "brotli", @@ -6415,7 +6471,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.108", + "syn 2.0.110", "tauri-utils", "thiserror 2.0.17", "time", @@ -6426,14 +6482,14 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f" +checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "tauri-codegen", "tauri-utils", ] @@ -6487,7 +6543,7 @@ dependencies = [ "thiserror 2.0.17", "tracing", "url", - "windows-registry", + "windows-registry 0.5.3", "windows-result 0.3.4", ] @@ -6769,10 +6825,11 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" dependencies = [ + "dunce", "embed-resource", "toml 0.9.8", ] @@ -6848,7 +6905,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6859,7 +6916,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -6978,7 +7035,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -7162,7 +7219,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -7187,7 +7244,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.108", + "syn 2.0.110", "tempfile", "tonic-build", ] @@ -7273,7 +7330,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -7352,14 +7409,13 @@ dependencies = [ [[package]] name = "tree_magic_mini" -version = "3.2.0" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f943391d896cdfe8eec03a04d7110332d445be7df856db382dd96a730667562c" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ "memchr", - "nom", - "once_cell", - "petgraph 0.6.5", + "nom 8.0.0", + "petgraph 0.8.3", ] [[package]] @@ -7553,7 +7609,7 @@ dependencies = [ "indexmap 2.12.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -7568,7 +7624,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.108", + "syn 2.0.110", "toml 0.9.8", "uniffi_meta", ] @@ -7866,7 +7922,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -8076,7 +8132,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -8096,14 +8152,14 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] name = "weezl" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "whoami" @@ -8284,7 +8340,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -8295,7 +8351,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -8341,6 +8397,17 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -8920,7 +8987,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "synstructure", ] @@ -8968,7 +9035,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "zbus_names", "zvariant", "zvariant_utils", @@ -9003,7 +9070,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -9023,7 +9090,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "synstructure", ] @@ -9044,7 +9111,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -9077,7 +9144,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", ] [[package]] @@ -9119,7 +9186,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.110", "zvariant_utils", ] @@ -9132,6 +9199,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.108", + "syn 2.0.110", "winnow 0.7.13", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ea2930b8..18b44c60 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 = "c99c0b209b19d9ce82e0f5d0d727261f8f9916b3" } +defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "9af5e79f60fbcf8ffa1460364d719cb1eec9f360" } dirs-next = "2.0" prost = "0.14" reqwest = { version = "0.12", features = ["cookies", "json"] } @@ -124,7 +124,10 @@ x25519-dalek = { version = "2", features = [ os_info = "3.12" [target.'cfg(target_os = "macos")'.dependencies] -swift-rs = "1.0" +block2 = "0.6" +objc2 = "0.6" +objc2-foundation = "0.3" +objc2-network-extension = "0.3" [target.'cfg(target_os = "macos")'.build-dependencies] swift-rs = { version = "1.0", features = ["build"] } diff --git a/src-tauri/build.rs b/src-tauri/build.rs index dd438547..91455f0e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -23,14 +23,6 @@ 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/src/apple.rs b/src-tauri/src/apple.rs new file mode 100644 index 00000000..1a8cf498 --- /dev/null +++ b/src-tauri/src/apple.rs @@ -0,0 +1,497 @@ +//! Structures used for interchangeability with the Swift code. + +use std::{ + hint::spin_loop, + net::IpAddr, + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::channel, + Arc, + }, +}; + +use block2::RcBlock; +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; +use objc2::{rc::Retained, runtime::AnyObject}; +use objc2_foundation::{ + ns_string, NSArray, NSDictionary, NSError, NSMutableArray, NSMutableDictionary, NSNull, + NSNumber, NSString, +}; +use objc2_network_extension::{NETunnelProviderManager, NETunnelProviderProtocol}; +use serde::Serialize; +use sqlx::SqliteExecutor; + +use crate::{ + database::models::{location::Location, tunnel::Tunnel, wireguard_keys::WireguardKeys, Id}, + error::Error, + utils::{DEFAULT_ROUTE_IPV4, DEFAULT_ROUTE_IPV6}, +}; + +// const BUNDLE_ID: &str = "net.defguard"; +const PLUGIN_BUNDLE_ID: &str = "net.defguard.VPNExtension"; + +// Should match the declaration in Swift. +#[repr(C)] +pub(crate) struct Stats { + pub(crate) location_id: Option, + pub(crate) tunnel_id: Option, + pub(crate) tx_bytes: u64, + pub(crate) rx_bytes: u64, + pub(crate) last_handshake: u64, +} + +/// Find `NETunnelProviderManager` in system preferences. +fn manager_for_name(name: &str) -> Option> { + let name_string = NSString::from_str(&name); + let plugin_bundle_id = NSString::from_str(PLUGIN_BUNDLE_ID); + let (tx, rx) = channel(); + + let handler = RcBlock::new( + move |managers_ptr: *mut NSArray, error_ptr: *mut NSError| { + if !error_ptr.is_null() { + error!("Failed to load tunnel provider managers."); + return; + } + + let Some(managers) = (unsafe { managers_ptr.as_ref() }) else { + error!("No managers"); + return; + }; + + for manager in managers { + let Some(vpn_protocol) = (unsafe { manager.protocolConfiguration() }) else { + continue; + }; + let Ok(tunnel_protocol) = vpn_protocol.downcast::() + else { + error!("Failed to downcast to NETunnelProviderProtocol"); + continue; + }; + // Sometimes all managers from all apps come through, so filter by bundle ID. + if let Some(bundle_id) = unsafe { tunnel_protocol.providerBundleIdentifier() } { + if bundle_id != plugin_bundle_id { + continue; + } + } + if let Some(descr) = unsafe { manager.localizedDescription() } { + error!("Descripion {descr}"); + if descr == name_string { + tx.send(Some(manager)).unwrap(); + return; + } + } + } + + tx.send(None).unwrap(); + }, + ); + unsafe { + NETunnelProviderManager::loadAllFromPreferencesWithCompletionHandler(&*handler); + } + + rx.recv().unwrap() +} + +#[derive(Serialize)] +pub(crate) struct TunnelConfiguration { + #[serde(rename = "locationId")] + location_id: Option, + #[serde(rename = "tunnelId")] + tunnel_id: Option, + 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 TunnelConfiguration { + /// Convert to `NSDictionary`. + fn as_nsdict(&self) -> Retained> { + let dict = NSMutableDictionary::new(); + + if let Some(location_id) = self.location_id { + dict.insert( + ns_string!("locationId"), + NSNumber::new_i64(location_id).as_ref(), + ); + } + + if let Some(tunnel_id) = self.tunnel_id { + dict.insert( + ns_string!("tunnelId"), + NSNumber::new_i64(tunnel_id).as_ref(), + ); + } + + dict.insert(ns_string!("name"), NSString::from_str(&self.name).as_ref()); + + dict.insert( + ns_string!("privateKey"), + NSString::from_str(&self.private_key).as_ref(), + ); + + // IpAddrMask + let addresses = NSMutableArray::>::new(); + for addr in &self.addresses { + let addr_dict = NSMutableDictionary::::new(); + addr_dict.insert( + ns_string!("address"), + NSString::from_str(&addr.address.to_string()).as_ref(), + ); + addr_dict.insert(ns_string!("cidr"), NSNumber::new_u8(addr.cidr).as_ref()); + addresses.addObject(addr_dict.into_super().as_ref()); + } + dict.insert(ns_string!("addresses"), addresses.as_ref()); + + if let Some(listen_port) = self.listen_port { + dict.insert( + ns_string!("listenPort"), + NSNumber::new_u16(listen_port).as_ref(), + ); + } + + // Peer + let peers = NSMutableArray::>::new(); + for peer in &self.peers { + let peer_dict = NSMutableDictionary::::new(); + peer_dict.insert( + ns_string!("public_key"), + NSString::from_str(&peer.public_key.to_string()).as_ref(), + ); + + if let Some(preshared_key) = &peer.preshared_key { + peer_dict.insert( + ns_string!("preshared_key"), + NSString::from_str(&preshared_key.to_string()).as_ref(), + ); + } + + if let Some(endpoint) = &peer.endpoint { + peer_dict.insert( + ns_string!("endpoint"), + NSString::from_str(&endpoint.to_string()).as_ref(), + ); + } + + // Skipping: last_handshake + + peer_dict.insert( + ns_string!("tx_bytes"), + NSNumber::new_u64(peer.tx_bytes).as_ref(), + ); + peer_dict.insert( + ns_string!("rx_bytes"), + NSNumber::new_u64(peer.rx_bytes).as_ref(), + ); + + if let Some(persistent_keep_alive) = peer.persistent_keepalive_interval { + peer_dict.insert( + ns_string!("persistent_keepalive_interval"), + NSNumber::new_u16(persistent_keep_alive).as_ref(), + ); + } + + // IpAddrMask + let allowed_ips = NSMutableArray::>::new(); + for addr in &peer.allowed_ips { + let addr_dict = NSMutableDictionary::::new(); + addr_dict.insert( + ns_string!("address"), + NSString::from_str(&addr.address.to_string()).as_ref(), + ); + addr_dict.insert(ns_string!("cidr"), NSNumber::new_u8(addr.cidr).as_ref()); + allowed_ips.addObject(addr_dict.into_super().as_ref()); + } + peer_dict.insert(ns_string!("allowed_ips"), allowed_ips.as_ref()); + + peers.addObject(peer_dict.into_super().as_ref()); + } + dict.insert(ns_string!("peers"), peers.into_super().as_ref()); + + if let Some(mtu) = self.mtu { + dict.insert(ns_string!("mtu"), NSNumber::new_u32(mtu).as_ref()); + } + + let dns = NSMutableArray::::new(); + for entry in &self.dns { + dns.addObject(NSString::from_str(&entry.to_string()).as_ref()); + } + dict.insert(ns_string!("dns"), dns.as_ref()); + + let dns_search = NSMutableArray::::new(); + for entry in &self.dns_search { + dns_search.addObject(NSString::from_str(&entry).as_ref()); + } + dict.insert(ns_string!("dnsSearch"), dns_search.as_ref()); + + dict.into_super() + } + + /// Create or update system VPN settings with this configuration. + pub(crate) fn save(&self) { + unsafe { + let provider_manager = + manager_for_name(&self.name).unwrap_or_else(|| NETunnelProviderManager::new()); + + let tunnel_protocol = NETunnelProviderProtocol::new(); + let plugin_bundle_id = NSString::from_str(PLUGIN_BUNDLE_ID); + tunnel_protocol.setProviderBundleIdentifier(Some(&plugin_bundle_id)); + let server_address = self.peers.get(0).map_or(String::new(), |peer| { + peer.endpoint.map_or(String::new(), |sa| sa.to_string()) + }); + let server_address = NSString::from_str(&server_address); + // `serverAddress` must have a non-nil string value for the protocol configuration to be + // valid. + tunnel_protocol.setServerAddress(Some(&server_address)); + + let provider_config = self.as_nsdict(); + tunnel_protocol.setProviderConfiguration(Some(&*provider_config)); + + provider_manager.setProtocolConfiguration(Some(&tunnel_protocol)); + let name = NSString::from_str(&self.name); + provider_manager.setLocalizedDescription(Some(&name)); + provider_manager.setEnabled(true); + + // Save to preferences. + let spinlock = Arc::new(AtomicBool::new(false)); + let spinlock_clone = Arc::clone(&spinlock); + let name = self.name.clone(); + let handler = RcBlock::new(move |error_ptr: *mut NSError| { + if error_ptr.is_null() { + info!("Saved tunnel configuration for {name}"); + } else { + error!("Failed to save tunnel configuration for: {name}"); + } + spinlock_clone.store(true, Ordering::Release); + }); + provider_manager.saveToPreferencesWithCompletionHandler(Some(&*handler)); + while !spinlock.load(Ordering::Acquire) { + spin_loop(); + } + } + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn start_tunnel(name: &str) { + if let Some(provider_manager) = manager_for_name(name) { + if let Err(err) = unsafe { provider_manager.connection().startVPNTunnelAndReturnError() } { + error!("Failed to start VPN: {err}"); + } else { + info!("VPN started"); + } + } else { + error!("Couldn't find configuration from preferences for {name}"); + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn stop_tunnel(name: &str) -> bool { + if let Some(provider_manager) = manager_for_name(name) { + unsafe { + provider_manager.connection().stopVPNTunnel(); + } + info!("VPN stopped"); + true + } else { + error!("Couldn't find configuration from preferences for {name}"); + false + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn all_tunnel_stats() -> Vec { + Vec::::new() +} + +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) + })?; + Ok(TunnelConfiguration { + location_id: Some(self.id), + tunnel_id: None, + name: self.name.clone(), + private_key: keys.prvkey, + addresses, + listen_port: Some(0), + peers: vec![peer], + mtu: None, + dns, + dns_search, + }) + } +} + +impl Tunnel { + pub(crate) async fn tunnel_configurarion<'e, E>( + &self, + executor: E, + dns: Vec, + dns_search: Vec, + ) -> Result + where + E: SqliteExecutor<'e>, + { + // prepare peer config + debug!("Decoding tunnel {self} public key: {}.", self.server_pubkey); + let peer_key = Key::from_str(&self.server_pubkey)?; + debug!("Tunnel {self} public key decoded."); + let mut peer = Peer::new(peer_key); + + debug!("Parsing tunnel {self} endpoint: {}", self.endpoint); + peer.set_endpoint(&self.endpoint)?; + peer.persistent_keepalive_interval = Some( + self.persistent_keep_alive + .try_into() + .expect("Failed to parse persistent keep alive"), + ); + debug!("Parsed tunnel {self} endpoint: {}", self.endpoint); + + if let Some(psk) = &self.preshared_key { + debug!("Decoding tunnel {self} preshared key."); + let peer_psk = Key::from_str(psk)?; + debug!("Preshared key for tunnel {self} decoded."); + peer.preshared_key = Some(peer_psk); + } + + debug!("Parsing tunnel {self} allowed ips: {:?}", self.allowed_ips); + let allowed_ips = if self.route_all_traffic { + debug!("Using all traffic routing for tunnel {self}"); + vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()] + } else { + let msg = match &self.allowed_ips { + Some(ips) => { + format!("Using predefined location traffic for tunnel {self}: {ips}") + } + None => "No allowed IP addresses found in tunnel {self} configuration".to_string(), + }; + debug!("{msg}"); + self.allowed_ips + .as_ref() + .map(|ips| ips.split(',').map(str::to_string).collect()) + .unwrap_or_default() + }; + for allowed_ip in &allowed_ips { + match IpAddrMask::from_str(allowed_ip.trim()) { + 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}: {err}"); + // Continue to the next iteration of the loop + } + } + } + debug!("Parsed tunnel {self} allowed IPs: {:?}", 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) + })?; + Ok(TunnelConfiguration { + location_id: None, + tunnel_id: Some(self.id), + name: self.name.clone(), + private_key: self.prvkey.clone(), + addresses, + listen_port: Some(0), + peers: vec![peer], + mtu: None, + dns, + dns_search, + }) + } +} diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index 3d5fabe8..4ff7c2cc 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -321,11 +321,24 @@ fn main() { // Handle shutdown. RunEvent::Exit => { debug!("Exiting the application's main event loop."); - tauri::async_runtime::block_on(async { + let handle = tauri::async_runtime::spawn(async { let _ = close_all_connections().await; // This will clean the database file, pruning write-ahead log. DB_POOL.close().await; }); + // Obj-C API needs a runtime, but at this point Tauri has closed its runtime, so + // create a temporary one. + #[cfg(target_os = "macos")] + { + use objc2_foundation::{NSDate, NSRunLoop}; + let run_loop = NSRunLoop::currentRunLoop(); + // Should be enough to quit. + let date = NSDate::dateWithTimeIntervalSinceNow(2.0); + run_loop.runUntilDate(&date); + } + tauri::async_runtime::block_on(async { + let _ = handle.await; + }); } _ => { trace!("Received event: {event:?}"); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 7577b4a9..14e9f09c 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -13,6 +13,13 @@ use tauri::{AppHandle, Emitter, Manager, State}; const UPDATE_URL: &str = "https://pkgs.defguard.net/api/update/check"; +#[cfg(target_os = "macos")] +use crate::apple::stop_tunnel; +#[cfg(not(target_os = "macos"))] +use crate::service::{ + proto::{DeleteServiceLocationsRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest}, + utils::DAEMON_CLIENT, +}; use crate::{ active_connections::{find_connection, get_connection_id_by_type}, app_config::{AppConfig, AppConfigPatch}, @@ -37,13 +44,6 @@ use crate::{ service_log_watcher::stop_log_watcher_task, }, proto::DeviceConfigResponse, - service::{ - proto::{ - DeleteServiceLocationsRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest, - ServiceLocation, - }, - utils::DAEMON_CLIENT, - }, tray::{configure_tray_icon, reload_tray_menu}, utils::{ construct_platform_header, disconnect_interface, execute_command, @@ -246,13 +246,13 @@ pub async fn save_device_config( let mut instance: Instance = instance_info.into(); if response.token.is_some() { debug!( - "The newly saved device config has a polling token, automatic configuration \ - polling will be possible if the core has an enterprise license." + "The newly saved device config has a polling token, automatic configuration polling \ + will be possible if the core has an enterprise license." ); } else { warn!( - "Missing polling token for instance {}, core and/or proxy services may need an \ - update, configuration polling won't work", + "Missing polling token for instance {}, core and/or proxy services may need an update, \ + configuration polling won't work", instance.name, ); } @@ -290,10 +290,38 @@ pub async fn save_device_config( transaction.commit().await?; info!("New instance {instance} created."); trace!("Created following instance: {instance:#?}"); + + let locations = push_service_locations(&instance, keys).await?; + + handle.emit(EventKey::InstanceUpdate.into(), ())?; + let res: SaveDeviceConfigResponse = SaveDeviceConfigResponse { + locations, + instance, + }; + reload_tray_menu(&handle).await; + + Ok(res) +} + +#[cfg(target_os = "macos")] +async fn push_service_locations( + instance: &Instance, + keys: WireguardKeys, +) -> Result>, Error> { + // Nothing here... yet + + Ok(Vec::new()) +} + +#[cfg(not(target_os = "macos"))] +async fn push_service_locations( + instance: &Instance, + keys: WireguardKeys, +) -> Result>, Error> { let locations = Location::find_by_instance_id(&*DB_POOL, instance.id, true).await?; trace!("Created following locations: {locations:#?}"); - let mut service_locations = Vec::::new(); + let mut service_locations = Vec::new(); for saved_location in &locations { if saved_location.is_service_location() { @@ -309,7 +337,7 @@ pub async fn save_device_config( let save_request = SaveServiceLocationsRequest { service_locations: service_locations.clone(), instance_id: instance.uuid.clone(), - private_key: keys.prvkey.clone(), + private_key: keys.prvkey, }; debug!( "Saving {} service locations to the daemon for instance {}({}).", @@ -334,14 +362,7 @@ pub async fn save_device_config( ); } - handle.emit(EventKey::InstanceUpdate.into(), ())?; - let res: SaveDeviceConfigResponse = SaveDeviceConfigResponse { - locations, - instance, - }; - reload_tray_menu(&handle).await; - - Ok(res) + Ok(locations) } #[tauri::command(async)] @@ -582,7 +603,7 @@ pub(crate) async fn do_update_instance( "A new base configuration has been applied to instance {instance}, even if nothing changed" ); - let mut service_locations = Vec::::new(); + let mut service_locations = Vec::new(); // check if locations have changed if locations_changed { @@ -591,7 +612,7 @@ pub(crate) async fn do_update_instance( "Updating locations for instance {}({}).", instance.name, instance.id ); - // fetch existing locations for given instance + // Fetch existing locations for a given instance. let mut current_locations = Location::find_by_instance_id(transaction.as_mut(), instance.id, true).await?; for dev_config in response.configs { @@ -663,10 +684,13 @@ pub(crate) async fn do_update_instance( "No service locations for instance {}({}), removing all existing service locations connections if there are any.", instance.name, instance.id ); - let delete_request = DeleteServiceLocationsRequest { - instance_id: instance.uuid.clone(), - }; - DAEMON_CLIENT + + #[cfg(not(target_os = "macos"))] + { + let delete_request = DeleteServiceLocationsRequest { + instance_id: instance.uuid.clone(), + }; + DAEMON_CLIENT .clone() .delete_service_locations(delete_request) .await @@ -677,10 +701,11 @@ pub(crate) async fn do_update_instance( ); Error::InternalError(err.to_string()) })?; - debug!( - "Successfully removed all service locations from daemon for instance {}({})", - instance.name, instance.id - ); + debug!( + "Successfully removed all service locations from daemon for instance {}({})", + instance.name, instance.id + ); + } } else { debug!( "Processing {} service location(s) for instance {}({})", @@ -689,42 +714,45 @@ pub(crate) async fn do_update_instance( instance.id ); - let save_request = SaveServiceLocationsRequest { - service_locations: service_locations.clone(), - instance_id: instance.uuid.clone(), - private_key: private_key.clone(), - }; + #[cfg(not(target_os = "macos"))] + { + let save_request = SaveServiceLocationsRequest { + service_locations: service_locations.clone(), + instance_id: instance.uuid.clone(), + private_key: private_key.clone(), + }; - debug!( - "Sending request to daemon to save {} service location(s) for instance {}({})", - save_request.service_locations.len(), - instance.name, - instance.id - ); + debug!( + "Sending request to daemon to save {} service location(s) for instance {}({})", + save_request.service_locations.len(), + instance.name, + instance.id + ); - DAEMON_CLIENT - .clone() - .save_service_locations(save_request) - .await - .map_err(|err| { - error!( + DAEMON_CLIENT + .clone() + .save_service_locations(save_request) + .await + .map_err(|err| { + error!( "Error while saving service locations to the daemon for instance {}({}): {err}", instance.name, instance.id, ); - Error::InternalError(err.to_string()) - })?; + Error::InternalError(err.to_string()) + })?; - info!( - "Successfully saved {} service location(s) to daemon for instance {}({})", - service_locations.len(), - instance.name, - instance.id - ); + info!( + "Successfully saved {} service location(s) to daemon for instance {}({})", + service_locations.len(), + instance.name, + instance.id + ); - debug!( - "Completed processing all service locations for instance {}({})", - instance.name, instance.id - ); + debug!( + "Completed processing all service locations for instance {}({})", + instance.name, instance.id + ); + } } Ok(()) @@ -937,6 +965,53 @@ pub async fn update_location_routing( } } +#[cfg(target_os = "macos")] +#[tauri::command(async)] +pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), Error> { + let app_state = handle.state::(); + let mut transaction = DB_POOL.begin().await?; + + let Some(instance) = Instance::find_by_id(&mut *transaction, instance_id).await? else { + error!("Couldn't delete instance: instance with ID {instance_id} could not be found."); + return Err(Error::NotFound); + }; + debug!("The instance that is being deleted has been identified as {instance}"); + + let instance_locations = + Location::find_by_instance_id(&mut *transaction, instance_id, false).await?; + if !instance_locations.is_empty() { + debug!( + "Found locations associated with the instance {instance}, closing their connections." + ); + } + for location in instance_locations { + if let Some(connection) = app_state + .remove_connection(location.id, ConnectionType::Location) + .await + { + let result = stop_tunnel(&location.name); + error!("stop_tunnel() for location returned {result:?}"); + if !result { + return Err(Error::InternalError("Error from tunnel".into())); + } + } + } + + instance.delete(&mut *transaction).await?; + + transaction.commit().await?; + + reload_tray_menu(&handle).await; + + let theme = { app_state.app_config.lock().unwrap().tray_theme }; + configure_tray_icon(&handle, theme).await?; + + handle.emit(EventKey::InstanceUpdate.into(), ())?; + info!("Successfully deleted instance {instance}."); + Ok(()) +} + +#[cfg(not(target_os = "macos"))] #[tauri::command(async)] pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), Error> { debug!("Deleting instance with ID {instance_id}"); @@ -989,8 +1064,7 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E transaction.commit().await?; - DAEMON_CLIENT - .clone() + client .delete_service_locations(DeleteServiceLocationsRequest { instance_id: instance.uuid.clone(), }) @@ -1005,7 +1079,6 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E reload_tray_menu(&handle).await; - let app_state: State = handle.state(); let theme = { app_state.app_config.lock().unwrap().tray_theme }; configure_tray_icon(&handle, theme).await?; @@ -1095,6 +1168,14 @@ pub async fn tunnel_details(tunnel_id: Id) -> Result, Error> { } } +#[cfg(target_os = "macos")] +#[tauri::command(async)] +pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error> { + // TODO: implementation + Ok(()) +} + +#[cfg(not(target_os = "macos"))] #[tauri::command(async)] pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error> { debug!("Deleting tunnel with ID {tunnel_id}"); diff --git a/src-tauri/src/database/models/instance.rs b/src-tauri/src/database/models/instance.rs index bcd74499..1a8339ba 100644 --- a/src-tauri/src/database/models/instance.rs +++ b/src-tauri/src/database/models/instance.rs @@ -1,4 +1,4 @@ -use core::fmt; +use std::fmt; use serde::{Deserialize, Serialize}; use sqlx::{query, query_as, SqliteExecutor}; diff --git a/src-tauri/src/database/models/tunnel.rs b/src-tauri/src/database/models/tunnel.rs index 7510ea0e..b53570d5 100644 --- a/src-tauri/src/database/models/tunnel.rs +++ b/src-tauri/src/database/models/tunnel.rs @@ -1,5 +1,4 @@ -use core::fmt; -use std::time::SystemTime; +use std::{fmt, time::SystemTime}; use chrono::{NaiveDateTime, Utc}; use defguard_wireguard_rs::host::Peer; @@ -256,7 +255,7 @@ impl Tunnel { #[derive(Debug, Serialize, Deserialize)] pub struct TunnelStats { id: I, - pub tunnel_id: Id, + pub(crate) tunnel_id: Id, upload: i64, download: i64, pub(crate) last_handshake: i64, diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs deleted file mode 100644 index ec89fd35..00000000 --- a/src-tauri/src/export.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! 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 e62a8b93..83ac4ee2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,14 +17,14 @@ use self::database::models::{Id, NoId}; pub mod active_connections; pub mod app_config; +#[cfg(target_os = "macos")] +mod apple; pub mod appstate; pub mod commands; 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; diff --git a/src-tauri/src/service/mod.rs b/src-tauri/src/service/mod.rs index cb90ebf4..711bf0f4 100644 --- a/src-tauri/src/service/mod.rs +++ b/src-tauri/src/service/mod.rs @@ -136,7 +136,7 @@ impl DesktopDaemonService for DaemonService { &self, _request: tonic::Request, ) -> Result, Status> { - debug!("Saved service location request received, this is currently not supported on Unix systems"); + debug!("Save service location request received, this is currently not supported on Unix systems"); Ok(Response::new(())) } @@ -145,7 +145,7 @@ impl DesktopDaemonService for DaemonService { &self, _request: tonic::Request, ) -> Result, Status> { - debug!("Saved service location request received, this is currently not supported on Unix systems"); + debug!("Delete service location request received, this is currently not supported on Unix systems"); Ok(Response::new(())) } diff --git a/src-tauri/src/service/utils.rs b/src-tauri/src/service/utils.rs index 0f51654f..a5612110 100644 --- a/src-tauri/src/service/utils.rs +++ b/src-tauri/src/service/utils.rs @@ -1,4 +1,6 @@ -use std::{io::stdout, sync::LazyLock}; +use std::io::stdout; +#[cfg(not(target_os = "macos"))] +use std::sync::LazyLock; use hyper_util::rt::TokioIo; #[cfg(windows)] @@ -22,6 +24,7 @@ use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY; use crate::service::named_pipe::PIPE_NAME; use crate::service::proto::desktop_daemon_service_client::DesktopDaemonServiceClient; +#[cfg(not(target_os = "macos"))] pub(crate) static DAEMON_CLIENT: LazyLock> = LazyLock::new(|| { debug!("Setting up gRPC client"); diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 7797602a..2f2ef9f1 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -7,8 +7,6 @@ use common::{find_free_tcp_port, get_interface_name}; use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration}; use prost::Message; use sqlx::query; -#[cfg(target_os = "macos")] -use swift_rs::SRString; use tauri::{AppHandle, Emitter, Manager}; #[cfg(not(target_os = "macos"))] use tonic::Code; @@ -24,7 +22,12 @@ 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::apple::{all_tunnel_stats, start_tunnel, stop_tunnel}; +#[cfg(not(target_os = "macos"))] +use crate::service::{ + proto::{CreateInterfaceRequest, ReadInterfaceDataRequest, RemoveInterfaceRequest}, + utils::DAEMON_CLIENT, +}; use crate::{ appstate::AppState, commands::LocationInterfaceDetails, @@ -43,15 +46,14 @@ use crate::{ events::EventKey, log_watcher::service_log_watcher::spawn_log_watcher_task, proto::ClientPlatformInfo, - service::{ - proto::{CreateInterfaceRequest, ReadInterfaceDataRequest, RemoveInterfaceRequest}, - utils::DAEMON_CLIENT, - }, ConnectionType, }; pub(crate) static DEFAULT_ROUTE_IPV4: &str = "0.0.0.0/0"; pub(crate) static DEFAULT_ROUTE_IPV6: &str = "::/0"; +// Work-around MFA propagation delay. FIXME: remove once Core API is corrected. +#[cfg(target_os = "macos")] +static TUNNEL_START_DELAY: Duration = Duration::from_secs(1); /// Setup client interface for `Instance`. #[cfg(not(target_os = "macos"))] @@ -133,28 +135,84 @@ pub(crate) async fn setup_interface( .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:?}"); - } + tunnel_config.save(); + tokio::time::sleep(TUNNEL_START_DELAY).await; + start_tunnel(&location.name); + Ok(interface_name) } #[cfg(target_os = "macos")] -pub(crate) async fn stats_handler(_pool: DbPool, name: String, _connection_type: ConnectionType) { +pub(crate) async fn stats_handler( + pool: DbPool, + _interface_name: String, + _connection_type: ConnectionType, +) { const CHECK_INTERVAL: Duration = Duration::from_secs(5); let mut interval = tokio::time::interval(CHECK_INTERVAL); loop { + info!("Stats 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); + + let all_stats = all_tunnel_stats(); + if all_stats.len() == 0 { + continue; + } + // Let `all_stats` be `Send`. + let all_stats = all_stats.as_slice().to_owned(); + + // let mut transaction = match pool.begin().await { + // Ok(transactions) => transactions, + // Err(err) => { + // error!( + // "Failed to begin database transaction for saving location/tunnel stats: {err}", + // ); + // continue; + // } + // }; + + for stats in all_stats { + info!( + "==> Stats: {} {} {}", + stats.last_handshake, stats.tx_bytes, stats.rx_bytes + ); + + if let Some(location_id) = stats.location_id { + //let location_stats = LocationStats { + //}; + /*match location_stats.save(&mut *transaction).await { + Ok(_) => { + debug!("Saved network usage stats for location ID {location_id}"); + } + Err(err) => { + error!( + "Failed to save network usage stats for location ID {location_id}: \ + {err}" + ); + } + }*/ + } + if let Some(tunnel_id) = stats.tunnel_id { + //let tunnel_stats = TunnelStats { + //}; + /*match tunnel_stats.save(&mut *transaction).await { + Ok(_) => { + debug!("Saved network usage stats for tunnel ID {tunnel_id}"); + } + Err(err) => { + error!( + "Failed to save network usage stats for tunnel ID {tunnel_id}: \ + {err}" + ); + } + }*/ + } } + + // if let Err(err) = transaction.commit().await { + // error!("Failed to commit database transaction for saving location/tunnel stats: {err}"); + // } } } @@ -297,7 +355,7 @@ pub fn load_log_targets() -> Vec { Vec::new() } -// helper function to get log file directory for the defguard-service daemon +/// Helper function to get log file directory for `defguard-service` daemon. #[must_use] pub fn get_service_log_dir() -> &'static Path { #[cfg(windows)] @@ -406,59 +464,64 @@ pub async fn setup_interface_tunnel( peers: vec![peer.clone()], mtu, }; - debug!("Creating interface {interface_config:?}"); - let request = CreateInterfaceRequest { - config: Some(interface_config.clone().into()), - dns: tunnel.dns.clone(), - }; - if let Some(pre_up) = &tunnel.pre_up { - debug!( - "Executing defined PreUp command before setting up the interface {} for the \ + + #[cfg(not(target_os = "macos"))] + { + debug!("Creating interface {interface_config:?}"); + let request = CreateInterfaceRequest { + config: Some(interface_config.clone().into()), + dns: tunnel.dns.clone(), + }; + if let Some(pre_up) = &tunnel.pre_up { + debug!( + "Executing defined PreUp command before setting up the interface {} for the \ tunnel {tunnel}: {pre_up}", - interface_config.name - ); - let _ = execute_command(pre_up); - info!( - "Executed defined PreUp command before setting up the interface {} for the \ + interface_config.name + ); + let _ = execute_command(pre_up); + info!( + "Executed defined PreUp command before setting up the interface {} for the \ tunnel {tunnel}: {pre_up}", - interface_config.name - ); - } - if let Err(error) = DAEMON_CLIENT.clone().create_interface(request).await { - error!( - "Failed to create a network interface ({}) for tunnel {tunnel}: {error}", - interface_config.name - ); - Err(Error::InternalError(format!( + interface_config.name + ); + } + if let Err(error) = DAEMON_CLIENT.clone().create_interface(request).await { + error!( + "Failed to create a network interface ({}) for tunnel {tunnel}: {error}", + interface_config.name + ); + return Err(Error::InternalError(format!( "Failed to create a network interface ({}) for tunnel {tunnel}, error message: {}. \ Check logs for more details.", interface_config.name, error.message() - ))) - } else { - info!( - "Network interface {} for tunnel {tunnel} created successfully.", - interface_config.name - ); - if let Some(post_up) = &tunnel.post_up { - debug!( - "Executing defined PostUp command after setting up the interface {} for the \ - tunnel {tunnel}: {post_up}", + ))); + } else { + info!( + "Network interface {} for tunnel {tunnel} created successfully.", interface_config.name ); - let _ = execute_command(post_up); - info!( - "Executed defined PostUp command after setting up the interface {} for the \ + if let Some(post_up) = &tunnel.post_up { + debug!( + "Executing defined PostUp command after setting up the interface {} for the \ + tunnel {tunnel}: {post_up}", + interface_config.name + ); + let _ = execute_command(post_up); + info!( + "Executed defined PostUp command after setting up the interface {} for the \ tunnel {tunnel}: {post_up}", + interface_config.name + ); + } + debug!( + "Created interface {} with config: {interface_config:?}", interface_config.name ); } - debug!( - "Created interface {} with config: {interface_config:?}", - interface_config.name - ); - Ok(interface_name) } + + Ok(interface_name) } pub async fn get_tunnel_interface_details( @@ -673,7 +736,6 @@ pub(crate) async fn disconnect_interface( "Disconnecting interface {}.", active_connection.interface_name ); - let mut client = DAEMON_CLIENT.clone(); let location_id = active_connection.location_id; let interface_name = active_connection.interface_name.clone(); @@ -689,13 +751,13 @@ pub(crate) async fn disconnect_interface( #[cfg(target_os = "macos")] { - let result = unsafe { - let name: SRString = location.name.as_str().into(); - stop_tunnel(&name) - }; - error!("stop_tunnel() returned {result:?}"); + let result = stop_tunnel(&location.name); + error!( + "stop_tunnel() for location {} returned {result:?}", + location.name + ); if !result { - return Err(Error::InternalError("Error from Swift".into())); + return Err(Error::InternalError("Error from tunnel".into())); } } @@ -710,7 +772,7 @@ pub(crate) async fn disconnect_interface( {}...", active_connection.interface_name, location.name ); - if let Err(error) = client.remove_interface(request).await { + if let Err(error) = DAEMON_CLIENT.clone().remove_interface(request).await { let msg = if error.code() == Code::Unavailable { format!( "Couldn't remove interface {}. Background service is unavailable. \ @@ -762,19 +824,35 @@ pub(crate) async fn disconnect_interface( active_connection.interface_name ); } - let request = RemoveInterfaceRequest { - interface_name, - endpoint: tunnel.endpoint.clone(), - }; - if let Err(error) = client.remove_interface(request).await { + + #[cfg(target_os = "macos")] + { + let result = stop_tunnel(&tunnel.name); error!( - "Error while removing interface {}, error details: {error:?}", - active_connection.interface_name + "stop_tunnel() for tunnel {} returned {result:?}", + tunnel.name ); - return Err(Error::InternalError(format!( - "Failed to remove interface, error message: {}", - error.message() - ))); + if !result { + return Err(Error::InternalError("Error from tunnel".into())); + } + } + + #[cfg(not(target_os = "macos"))] + { + let request = RemoveInterfaceRequest { + interface_name, + endpoint: tunnel.endpoint.clone(), + }; + if let Err(error) = DAEMON_CLIENT.clone().remove_interface(request).await { + error!( + "Error while removing interface {}, error details: {error:?}", + active_connection.interface_name + ); + return Err(Error::InternalError(format!( + "Failed to remove interface, error message: {}", + error.message() + ))); + } } if let Some(post_down) = &tunnel.post_down { debug!( diff --git a/swift/boringtun b/swift/boringtun index f47e80a9..20d92e55 160000 --- a/swift/boringtun +++ b/swift/boringtun @@ -1 +1 @@ -Subproject commit f47e80a96923733bb9ed2bd5590f882dfb1d9b95 +Subproject commit 20d92e555303ae2d7c54f759c3cb4b222eec9d54 diff --git a/swift/build.sh b/swift/build.sh index 04112714..c514ccab 100755 --- a/swift/build.sh +++ b/swift/build.sh @@ -45,9 +45,9 @@ popd # Build VPNExtension. -# if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then +if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then CONFIG=Release -# else -# CONFIG=Debug -# fi +else + CONFIG=Debug +fi xcodebuild -project extension/VPNExtension.xcodeproj -target VPNExtension -configuration ${CONFIG} build diff --git a/swift/extension/VPNExtension/Adapter.swift b/swift/extension/VPNExtension/Adapter.swift index f60864c7..0899bed9 100644 --- a/swift/extension/VPNExtension/Adapter.swift +++ b/swift/extension/VPNExtension/Adapter.swift @@ -31,6 +31,10 @@ enum State { /// Adapter state. private var state: State = .stopped + /// For statistics returned to Rust code. + var locationId: UInt64? + var tunnelId: UInt64? + private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() /// Designated initializer. @@ -71,6 +75,8 @@ enum State { keepAlive: tunnelConfiguration.peers[0].persistentKeepAlive, index: 0 ) + locationId = tunnelConfiguration.locationId + tunnelId = tunnelConfiguration.tunnelId logger.info("Connecting to endpoint") guard let endpoint = tunnelConfiguration.peers[0].endpoint else { @@ -84,10 +90,6 @@ enum State { readPackets() state = .running - - // Test notifications - // let notificationName = CFNotificationName("net.defguard.client.start" as CFString) - // CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false) } func stop() { @@ -103,16 +105,18 @@ enum State { 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 Stats( + txBytes: stats.txBytes, + rxBytes: stats.rxBytes, + lastHandshake: stats.lastHandshake, + locationId: locationId, + tunnelId: tunnelId + ) } return nil } diff --git a/swift/extension/VPNExtension/PacketTunnelProvider.swift b/swift/extension/VPNExtension/PacketTunnelProvider.swift index addccb60..55c1d18f 100644 --- a/swift/extension/VPNExtension/PacketTunnelProvider.swift +++ b/swift/extension/VPNExtension/PacketTunnelProvider.swift @@ -22,6 +22,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { guard let protocolConfig = self.protocolConfiguration as? NETunnelProviderProtocol, let providerConfig = protocolConfig.providerConfiguration, let tunnelConfig = try? TunnelConfiguration.from(dictionary: providerConfig) else { + self.logger.error("Failed to parse tunnel configuration") completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) return } diff --git a/swift/plugin/Sources/Defguard/Stats.swift b/swift/plugin/Sources/Defguard/Stats.swift index b7f97569..2c833446 100644 --- a/swift/plugin/Sources/Defguard/Stats.swift +++ b/swift/plugin/Sources/Defguard/Stats.swift @@ -3,9 +3,16 @@ import ObjectiveC public class Stats: NSObject, Codable { var txBytes: UInt64 var rxBytes: UInt64 + var lastHandshake: UInt64 + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? - init(txBytes: UInt64, rxBytes: UInt64) { + init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, tunnelId: UInt64?) { self.txBytes = txBytes self.rxBytes = rxBytes + self.lastHandshake = lastHandshake + self.locationId = locationId + self.tunnelId = tunnelId } } diff --git a/swift/plugin/Sources/Defguard/TunnelConfiguration.swift b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift index a19c1e65..ded41f36 100644 --- a/swift/plugin/Sources/Defguard/TunnelConfiguration.swift +++ b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift @@ -2,6 +2,10 @@ import Foundation import NetworkExtension final class TunnelConfiguration: Codable { + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? + var name: String var privateKey: String var addresses: [IpAddrMask] = [] @@ -78,8 +82,9 @@ final class TunnelConfiguration: Codable { // 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())") + 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 { @@ -97,12 +102,14 @@ final class TunnelConfiguration: Codable { for addr_mask in peer.allowedIPs { if addr_mask.address is IPv4Address { ipv4IncludedRoutes.append( - NEIPv4Route(destinationAddress: "\(addr_mask.address)", - subnetMask: "\(addr_mask.mask())")) + 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))) + NEIPv6Route( + destinationAddress: "\(addr_mask.address)", + networkPrefixLength: NSNumber(value: addr_mask.cidr))) } } } diff --git a/swift/plugin/Sources/Shared.swift b/swift/plugin/Sources/Shared.swift deleted file mode 100644 index 35eaaacc..00000000 --- a/swift/plugin/Sources/Shared.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// 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 deleted file mode 100644 index 213ed1af..00000000 --- a/swift/plugin/Sources/VPNError.swift +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index e6627337..00000000 --- a/swift/plugin/Sources/VPNManager.swift +++ /dev/null @@ -1,173 +0,0 @@ -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 index 5788821a..16a20d78 100644 --- a/swift/plugin/Sources/Wireguard.swift +++ b/swift/plugin/Sources/Wireguard.swift @@ -6,7 +6,6 @@ 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. @@ -135,6 +134,71 @@ public func tunnelStats(name: SRString) -> Stats? { return result } +@_cdecl("all_tunnel_stats") +public func allTunnelStats() -> SRObjectArray { + // Blocking + let semaphore = DispatchSemaphore(value: 0) + var stats: [Stats] = [] + + // Get all tunnel provider managers. + 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)") + semaphore.signal() + return + } + logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.") + + // `NETunnelProviderSession.sendProviderMessage()` is asynchronous, so use `DispatchGroup`. + let dispatchGroup = DispatchGroup() + + for manager in managers { + 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 { + continue + } + if let providerManager = manager as NETunnelProviderManager? { + let session = providerManager.connection as! NETunnelProviderSession + do { + // TODO: data should contain a valid message. + let data = Data() + dispatchGroup.enter() + try session.sendProviderMessage(data) { response in + if let data = response { + let decoder = JSONDecoder() + if let result = try? decoder.decode(Stats.self, from: data) { + stats.append(result) + } + } + dispatchGroup.leave() + } + } catch { + logger.error("Failed to send message to tunnel extension \(error)") + dispatchGroup.leave() + } + } + } + + // NOTE: `dispatchGroup.wait()` will cause a dead-lock, because it uses the same thread as + // `NETunnelProviderSession.sendProviderMessage()`. Use this pattern instead: + dispatchGroup.notify(queue: DispatchQueue.global()) { + semaphore.signal() + } + } + + semaphore.wait() + return SRObjectArray(stats) +} + /// Save `TunnelConfiguration` to preferences. func saveConfig(_ config: TunnelConfiguration) { // Blocking @@ -181,7 +245,6 @@ func saveConfig(_ config: TunnelConfiguration) { semaphore.wait() } - /// Start VPN tunnel for a given `name`. func startVPN(name: String) { managerForName(name) { manager in diff --git a/swift/plugin/Sources/WireguardPlugin.swift b/swift/plugin/Sources/WireguardPlugin.swift deleted file mode 100644 index 357273b5..00000000 --- a/swift/plugin/Sources/WireguardPlugin.swift +++ /dev/null @@ -1,485 +0,0 @@ -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, - ) - ) - } - } -}