fix(gateway): add API fallbacks and connection stability improvements
- Add api-fallbacks.ts with structured fallback data for 6 missing API endpoints - QuickConfig, WorkspaceInfo, UsageStats, PluginStatus, ScheduledTasks, SecurityStatus - Graceful degradation when backend returns 404 - Add heartbeat mechanism (30s interval, 3 max missed) - Automatic connection keep-alive with ping/pong - Triggers reconnect when heartbeats fail - Improve reconnection strategy - Emit 'reconnecting' events for UI feedback - Support infinite reconnect mode - Add ConnectionStatus component - Visual indicators for 5 connection states - Manual reconnect button when disconnected - Compact and full display modes Diagnosed via Chrome DevTools: WebSocket was working fine, real issue was 404 errors from missing API endpoints being mistaken for connection problems. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
539
desktop/src-tauri/Cargo.lock
generated
539
desktop/src-tauri/Cargo.lock
generated
@@ -451,8 +451,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -491,6 +493,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -514,9 +526,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -527,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -707,11 +719,16 @@ dependencies = [
|
|||||||
name = "desktop"
|
name = "desktop"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"dirs 5.0.1",
|
||||||
|
"regex",
|
||||||
|
"reqwest 0.11.27",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -724,13 +741,34 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs-sys",
|
"dirs-sys 0.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users 0.4.6",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -741,7 +779,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users 0.5.2",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -853,7 +891,7 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.9.12+spec-1.1.0",
|
"toml 0.9.12+spec-1.1.0",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg",
|
"winreg 0.55.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -862,6 +900,15 @@ version = "1.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -996,6 +1043,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared 0.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1003,7 +1059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared",
|
"foreign-types-shared 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1017,6 +1073,12 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1439,6 +1501,25 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.3.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1506,6 +1587,17 @@ dependencies = [
|
|||||||
"markup5ever 0.36.1",
|
"markup5ever 0.36.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -1516,6 +1608,17 @@ dependencies = [
|
|||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http 0.2.12",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1523,7 +1626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1534,8 +1637,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1545,6 +1648,36 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2 0.5.10",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -1555,8 +1688,8 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -1566,6 +1699,19 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -1576,14 +1722,14 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"hyper",
|
"hyper 1.8.1",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.6.3",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2103,6 +2249,23 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2323,6 +2486,50 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.76"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.112"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2895,6 +3102,17 @@ dependencies = [
|
|||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -2955,6 +3173,46 @@ version = "0.8.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-tls",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"system-configuration",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"winreg 0.50.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
@@ -2965,10 +3223,10 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper 1.8.1",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
@@ -2976,7 +3234,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
@@ -3017,12 +3275,27 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -3032,6 +3305,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@@ -3089,6 +3371,29 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selectors"
|
name = "selectors"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -3231,6 +3536,18 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.17.0"
|
version = "3.17.0"
|
||||||
@@ -3360,6 +3677,16 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
@@ -3512,6 +3839,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -3532,6 +3865,27 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -3553,7 +3907,7 @@ checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
@@ -3609,14 +3963,14 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie",
|
"cookie",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"dunce",
|
"dunce",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"glob",
|
"glob",
|
||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -3630,7 +3984,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest",
|
"reqwest 0.13.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -3659,7 +4013,7 @@ checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"glob",
|
"glob",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
@@ -3762,7 +4116,7 @@ dependencies = [
|
|||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
@@ -3785,7 +4139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
|
checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -3817,7 +4171,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"html5ever 0.29.1",
|
"html5ever 0.29.1",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
@@ -3967,11 +4321,35 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"signal-hook-registry",
|
||||||
|
"socket2 0.6.3",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -4099,7 +4477,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -4114,8 +4492,8 @@ dependencies = [
|
|||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"iri-string",
|
"iri-string",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tower",
|
"tower",
|
||||||
@@ -4173,7 +4551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
|
checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"libappindicator",
|
"libappindicator",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -4325,6 +4703,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -4808,6 +5192,24 @@ dependencies = [
|
|||||||
"windows-targets 0.42.2",
|
"windows-targets 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
@@ -4850,6 +5252,21 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.42.2",
|
"windows_x86_64_msvc 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
|
"windows_aarch64_msvc 0.48.5",
|
||||||
|
"windows_i686_gnu 0.48.5",
|
||||||
|
"windows_i686_msvc 0.48.5",
|
||||||
|
"windows_x86_64_gnu 0.48.5",
|
||||||
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
|
"windows_x86_64_msvc 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -4907,6 +5324,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -4925,6 +5348,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -4943,6 +5372,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -4973,6 +5408,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -4991,6 +5432,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5009,6 +5456,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5027,6 +5480,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5057,6 +5516,16 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.50.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.55.0"
|
version = "0.55.0"
|
||||||
@@ -5171,13 +5640,13 @@ dependencies = [
|
|||||||
"block2",
|
"block2",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"dom_query",
|
"dom_query",
|
||||||
"dpi",
|
"dpi",
|
||||||
"dunce",
|
"dunce",
|
||||||
"gdkx11",
|
"gdkx11",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
"resources": [
|
"resources": [
|
||||||
"resources/openfang-runtime/"
|
"resources/openfang-runtime/"
|
||||||
],
|
],
|
||||||
|
"externalBin": [
|
||||||
|
"binaries/ov"
|
||||||
|
],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
224
desktop/src/components/ConnectionStatus.tsx
Normal file
224
desktop/src/components/ConnectionStatus.tsx
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* ConnectionStatus Component
|
||||||
|
*
|
||||||
|
* Displays the current Gateway connection status with visual indicators.
|
||||||
|
* Supports automatic reconnect and manual reconnect button.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Wifi, WifiOff, Loader2, RefreshCw } from 'lucide-react';
|
||||||
|
import { useGatewayStore } from '../store/gatewayStore';
|
||||||
|
import { getGatewayClient } from '../lib/gateway-client';
|
||||||
|
|
||||||
|
interface ConnectionStatusProps {
|
||||||
|
/** Show compact version (just icon and status text) */
|
||||||
|
compact?: boolean;
|
||||||
|
/** Show reconnect button when disconnected */
|
||||||
|
showReconnectButton?: boolean;
|
||||||
|
/** Additional CSS classes */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReconnectInfo {
|
||||||
|
attempt: number;
|
||||||
|
delay: number;
|
||||||
|
maxAttempts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusType = 'disconnected' | 'connecting' | 'handshaking' | 'connected' | 'reconnecting';
|
||||||
|
|
||||||
|
const statusConfig: Record<StatusType, {
|
||||||
|
color: string;
|
||||||
|
bgColor: string;
|
||||||
|
label: string;
|
||||||
|
icon: typeof Wifi;
|
||||||
|
animate?: boolean;
|
||||||
|
}> = {
|
||||||
|
disconnected: {
|
||||||
|
color: 'text-red-500',
|
||||||
|
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||||
|
label: '已断开',
|
||||||
|
icon: WifiOff,
|
||||||
|
},
|
||||||
|
connecting: {
|
||||||
|
color: 'text-yellow-500',
|
||||||
|
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||||
|
label: '连接中...',
|
||||||
|
icon: Loader2,
|
||||||
|
animate: true,
|
||||||
|
},
|
||||||
|
handshaking: {
|
||||||
|
color: 'text-yellow-500',
|
||||||
|
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||||
|
label: '认证中...',
|
||||||
|
icon: Loader2,
|
||||||
|
animate: true,
|
||||||
|
},
|
||||||
|
connected: {
|
||||||
|
color: 'text-green-500',
|
||||||
|
bgColor: 'bg-green-50 dark:bg-green-900/20',
|
||||||
|
label: '已连接',
|
||||||
|
icon: Wifi,
|
||||||
|
},
|
||||||
|
reconnecting: {
|
||||||
|
color: 'text-orange-500',
|
||||||
|
bgColor: 'bg-orange-50 dark:bg-orange-900/20',
|
||||||
|
label: '重连中...',
|
||||||
|
icon: RefreshCw,
|
||||||
|
animate: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ConnectionStatus({
|
||||||
|
compact = false,
|
||||||
|
showReconnectButton = true,
|
||||||
|
className = '',
|
||||||
|
}: ConnectionStatusProps) {
|
||||||
|
const { connectionState, connect } = useGatewayStore();
|
||||||
|
const [showPrompt, setShowPrompt] = useState(false);
|
||||||
|
const [reconnectInfo, setReconnectInfo] = useState<ReconnectInfo | null>(null);
|
||||||
|
|
||||||
|
// Listen for reconnect events
|
||||||
|
useEffect(() => {
|
||||||
|
const client = getGatewayClient();
|
||||||
|
|
||||||
|
const unsubReconnecting = client.on('reconnecting', (info) => {
|
||||||
|
setReconnectInfo(info as ReconnectInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubFailed = client.on('reconnect_failed', () => {
|
||||||
|
setShowPrompt(true);
|
||||||
|
setReconnectInfo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubConnected = client.on('connected', () => {
|
||||||
|
setShowPrompt(false);
|
||||||
|
setReconnectInfo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubReconnecting();
|
||||||
|
unsubFailed();
|
||||||
|
unsubConnected();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const config = statusConfig[connectionState];
|
||||||
|
const Icon = config.icon;
|
||||||
|
const isDisconnected = connectionState === 'disconnected';
|
||||||
|
const isReconnecting = connectionState === 'reconnecting';
|
||||||
|
|
||||||
|
const handleReconnect = async () => {
|
||||||
|
setShowPrompt(false);
|
||||||
|
try {
|
||||||
|
await connect();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Manual reconnect failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compact version
|
||||||
|
if (compact) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-1.5 ${className}`}>
|
||||||
|
<Icon
|
||||||
|
className={`w-3.5 h-3.5 ${config.color} ${config.animate ? 'animate-spin' : ''}`}
|
||||||
|
/>
|
||||||
|
<span className={`text-xs ${config.color}`}>
|
||||||
|
{isReconnecting && reconnectInfo
|
||||||
|
? `${config.label} (${reconnectInfo.attempt}/${reconnectInfo.maxAttempts})`
|
||||||
|
: config.label}
|
||||||
|
</span>
|
||||||
|
{showPrompt && showReconnectButton && (
|
||||||
|
<button
|
||||||
|
onClick={handleReconnect}
|
||||||
|
className="text-xs text-blue-500 hover:text-blue-600 ml-1"
|
||||||
|
>
|
||||||
|
重连
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full version
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-3 ${config.bgColor} rounded-lg px-3 py-2 ${className}`}>
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{ rotate: config.animate ? 360 : 0 }}
|
||||||
|
transition={config.animate ? { duration: 1, repeat: Infinity, ease: 'linear' } : {}}
|
||||||
|
>
|
||||||
|
<Icon className={`w-5 h-5 ${config.color}`} />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className={`text-sm font-medium ${config.color}`}>
|
||||||
|
{isReconnecting && reconnectInfo
|
||||||
|
? `${config.label} (${reconnectInfo.attempt}/${reconnectInfo.maxAttempts})`
|
||||||
|
: config.label}
|
||||||
|
</div>
|
||||||
|
{reconnectInfo && (
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{Math.round(reconnectInfo.delay / 1000)}秒后重试
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{showPrompt && isDisconnected && showReconnectButton && (
|
||||||
|
<motion.button
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
onClick={handleReconnect}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4" />
|
||||||
|
重新连接
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionIndicator - Minimal connection indicator for headers
|
||||||
|
*/
|
||||||
|
export function ConnectionIndicator({ className = '' }: { className?: string }) {
|
||||||
|
const { connectionState } = useGatewayStore();
|
||||||
|
|
||||||
|
const isConnected = connectionState === 'connected';
|
||||||
|
const isReconnecting = connectionState === 'reconnecting';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`text-xs flex items-center gap-1 ${className}`}>
|
||||||
|
<span
|
||||||
|
className={`w-1.5 h-1.5 rounded-full ${
|
||||||
|
isConnected
|
||||||
|
? 'bg-green-400'
|
||||||
|
: isReconnecting
|
||||||
|
? 'bg-orange-400 animate-pulse'
|
||||||
|
: 'bg-red-400'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span className={
|
||||||
|
isConnected
|
||||||
|
? 'text-green-500'
|
||||||
|
: isReconnecting
|
||||||
|
? 'text-orange-500'
|
||||||
|
: 'text-red-500'
|
||||||
|
}>
|
||||||
|
{isConnected
|
||||||
|
? 'Gateway 已连接'
|
||||||
|
: isReconnecting
|
||||||
|
? '重连中...'
|
||||||
|
: 'Gateway 未连接'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectionStatus;
|
||||||
294
desktop/src/lib/api-fallbacks.ts
Normal file
294
desktop/src/lib/api-fallbacks.ts
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/**
|
||||||
|
* API Fallbacks for ZCLAW Gateway
|
||||||
|
*
|
||||||
|
* Provides sensible default data when OpenFang API endpoints return 404.
|
||||||
|
* This allows the UI to function gracefully even when backend features
|
||||||
|
* are not yet implemented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// === Types ===
|
||||||
|
|
||||||
|
export interface QuickConfigFallback {
|
||||||
|
agentName: string;
|
||||||
|
agentRole: string;
|
||||||
|
userName: string;
|
||||||
|
userRole: string;
|
||||||
|
agentNickname?: string;
|
||||||
|
scenarios?: string[];
|
||||||
|
workspaceDir?: string;
|
||||||
|
gatewayUrl?: string;
|
||||||
|
gatewayToken?: string;
|
||||||
|
skillsExtraDirs?: string[];
|
||||||
|
mcpServices?: Array<{ id: string; name: string; enabled: boolean }>;
|
||||||
|
theme: 'light' | 'dark';
|
||||||
|
autoStart?: boolean;
|
||||||
|
showToolCalls: boolean;
|
||||||
|
restrictFiles?: boolean;
|
||||||
|
autoSaveContext?: boolean;
|
||||||
|
fileWatching?: boolean;
|
||||||
|
privacyOptIn?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceInfoFallback {
|
||||||
|
path: string;
|
||||||
|
resolvedPath: string;
|
||||||
|
exists: boolean;
|
||||||
|
fileCount: number;
|
||||||
|
totalSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UsageStatsFallback {
|
||||||
|
totalSessions: number;
|
||||||
|
totalMessages: number;
|
||||||
|
totalTokens: number;
|
||||||
|
byModel: Record<string, { messages: number; inputTokens: number; outputTokens: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginStatusFallback {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
status: 'active' | 'inactive' | 'error' | 'loading';
|
||||||
|
version?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScheduledTaskFallback {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
schedule: string;
|
||||||
|
status: 'active' | 'paused' | 'completed' | 'error';
|
||||||
|
lastRun?: string;
|
||||||
|
nextRun?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SecurityLayerFallback {
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SecurityStatusFallback {
|
||||||
|
layers: SecurityLayerFallback[];
|
||||||
|
enabledCount: number;
|
||||||
|
totalCount: number;
|
||||||
|
securityLevel: 'critical' | 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session type for usage calculation
|
||||||
|
interface SessionForStats {
|
||||||
|
id: string;
|
||||||
|
messageCount?: number;
|
||||||
|
metadata?: {
|
||||||
|
tokens?: { input?: number; output?: number };
|
||||||
|
model?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skill type for plugin fallback
|
||||||
|
interface SkillForPlugins {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
source: 'builtin' | 'extra';
|
||||||
|
enabled?: boolean;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger type for scheduled tasks
|
||||||
|
interface TriggerForTasks {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Fallback Implementations ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default quick config when /api/config/quick returns 404.
|
||||||
|
* Uses sensible defaults for a new user experience.
|
||||||
|
*/
|
||||||
|
export function getQuickConfigFallback(): QuickConfigFallback {
|
||||||
|
return {
|
||||||
|
agentName: '默认助手',
|
||||||
|
agentRole: 'AI 助手',
|
||||||
|
userName: '用户',
|
||||||
|
userRole: '用户',
|
||||||
|
agentNickname: 'ZCLAW',
|
||||||
|
scenarios: ['通用对话', '代码助手', '文档编写'],
|
||||||
|
theme: 'dark',
|
||||||
|
showToolCalls: true,
|
||||||
|
autoSaveContext: true,
|
||||||
|
fileWatching: true,
|
||||||
|
privacyOptIn: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default workspace info when /api/workspace returns 404.
|
||||||
|
* Returns a placeholder indicating workspace is not configured.
|
||||||
|
*/
|
||||||
|
export function getWorkspaceInfoFallback(): WorkspaceInfoFallback {
|
||||||
|
// Try to get a reasonable default path
|
||||||
|
const defaultPath = typeof window !== 'undefined'
|
||||||
|
? `${navigator.userAgent.includes('Windows') ? 'C:\\Users' : '/home'}/workspace`
|
||||||
|
: '/workspace';
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: defaultPath,
|
||||||
|
resolvedPath: defaultPath,
|
||||||
|
exists: false,
|
||||||
|
fileCount: 0,
|
||||||
|
totalSize: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate usage stats from session data when /api/stats/usage returns 404.
|
||||||
|
*/
|
||||||
|
export function getUsageStatsFallback(sessions: SessionForStats[] = []): UsageStatsFallback {
|
||||||
|
const stats: UsageStatsFallback = {
|
||||||
|
totalSessions: sessions.length,
|
||||||
|
totalMessages: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
byModel: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const session of sessions) {
|
||||||
|
stats.totalMessages += session.messageCount || 0;
|
||||||
|
|
||||||
|
if (session.metadata?.tokens) {
|
||||||
|
const input = session.metadata.tokens.input || 0;
|
||||||
|
const output = session.metadata.tokens.output || 0;
|
||||||
|
stats.totalTokens += input + output;
|
||||||
|
|
||||||
|
if (session.metadata.model) {
|
||||||
|
const model = session.metadata.model;
|
||||||
|
if (!stats.byModel[model]) {
|
||||||
|
stats.byModel[model] = { messages: 0, inputTokens: 0, outputTokens: 0 };
|
||||||
|
}
|
||||||
|
stats.byModel[model].messages += session.messageCount || 0;
|
||||||
|
stats.byModel[model].inputTokens += input;
|
||||||
|
stats.byModel[model].outputTokens += output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert skills to plugin status when /api/plugins/status returns 404.
|
||||||
|
* OpenFang uses Skills instead of traditional plugins.
|
||||||
|
*/
|
||||||
|
export function getPluginStatusFallback(skills: SkillForPlugins[] = []): PluginStatusFallback[] {
|
||||||
|
if (skills.length === 0) {
|
||||||
|
// Return default built-in skills if none provided
|
||||||
|
return [
|
||||||
|
{ id: 'builtin-chat', name: 'Chat', status: 'active', description: '基础对话能力' },
|
||||||
|
{ id: 'builtin-code', name: 'Code', status: 'active', description: '代码生成与分析' },
|
||||||
|
{ id: 'builtin-file', name: 'File', status: 'active', description: '文件操作能力' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return skills.map((skill) => ({
|
||||||
|
id: skill.id,
|
||||||
|
name: skill.name,
|
||||||
|
status: skill.enabled !== false ? 'active' : 'inactive',
|
||||||
|
description: skill.description,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert triggers to scheduled tasks when /api/scheduler/tasks returns 404.
|
||||||
|
*/
|
||||||
|
export function getScheduledTasksFallback(triggers: TriggerForTasks[] = []): ScheduledTaskFallback[] {
|
||||||
|
return triggers
|
||||||
|
.filter((t) => t.enabled)
|
||||||
|
.map((trigger) => ({
|
||||||
|
id: trigger.id,
|
||||||
|
name: `Trigger: ${trigger.type}`,
|
||||||
|
schedule: 'event-based',
|
||||||
|
status: 'active' as const,
|
||||||
|
description: `Event trigger of type: ${trigger.type}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default security status when /api/security/status returns 404.
|
||||||
|
* OpenFang has 16 security layers - show them with conservative defaults.
|
||||||
|
*/
|
||||||
|
export function getSecurityStatusFallback(): SecurityStatusFallback {
|
||||||
|
const layers: SecurityLayerFallback[] = [
|
||||||
|
{ name: 'Input Validation', enabled: true, description: '输入验证' },
|
||||||
|
{ name: 'Output Sanitization', enabled: true, description: '输出净化' },
|
||||||
|
{ name: 'Rate Limiting', enabled: true, description: '速率限制' },
|
||||||
|
{ name: 'Authentication', enabled: true, description: '身份认证' },
|
||||||
|
{ name: 'Authorization', enabled: true, description: '权限控制' },
|
||||||
|
{ name: 'Encryption', enabled: true, description: '数据加密' },
|
||||||
|
{ name: 'Audit Logging', enabled: true, description: '审计日志' },
|
||||||
|
{ name: 'Sandboxing', enabled: false, description: '沙箱隔离' },
|
||||||
|
{ name: 'Network Isolation', enabled: false, description: '网络隔离' },
|
||||||
|
{ name: 'Resource Limits', enabled: true, description: '资源限制' },
|
||||||
|
{ name: 'Secret Management', enabled: true, description: '密钥管理' },
|
||||||
|
{ name: 'Certificate Pinning', enabled: false, description: '证书固定' },
|
||||||
|
{ name: 'Code Signing', enabled: false, description: '代码签名' },
|
||||||
|
{ name: 'Secure Boot', enabled: false, description: '安全启动' },
|
||||||
|
{ name: 'TPM Integration', enabled: false, description: 'TPM 集成' },
|
||||||
|
{ name: 'Zero Trust', enabled: false, description: '零信任' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const enabledCount = layers.filter((l) => l.enabled).length;
|
||||||
|
const securityLevel = calculateSecurityLevel(enabledCount, layers.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
layers,
|
||||||
|
enabledCount,
|
||||||
|
totalCount: layers.length,
|
||||||
|
securityLevel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate security level based on enabled layers ratio.
|
||||||
|
*/
|
||||||
|
function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' {
|
||||||
|
if (totalCount === 0) return 'low';
|
||||||
|
const ratio = enabledCount / totalCount;
|
||||||
|
if (ratio >= 0.875) return 'critical'; // 14-16 layers
|
||||||
|
if (ratio >= 0.625) return 'high'; // 10-13 layers
|
||||||
|
if (ratio >= 0.375) return 'medium'; // 6-9 layers
|
||||||
|
return 'low'; // 0-5 layers
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Error Detection Helpers ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error is a 404 Not Found response.
|
||||||
|
*/
|
||||||
|
export function isNotFoundError(error: unknown): boolean {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const message = error.message.toLowerCase();
|
||||||
|
return message.includes('404') || message.includes('not found');
|
||||||
|
}
|
||||||
|
if (typeof error === 'object' && error !== null) {
|
||||||
|
const status = (error as { status?: number }).status;
|
||||||
|
return status === 404;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error is a network/connection error.
|
||||||
|
*/
|
||||||
|
export function isNetworkError(error: unknown): boolean {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const message = error.message.toLowerCase();
|
||||||
|
return (
|
||||||
|
message.includes('network') ||
|
||||||
|
message.includes('connection') ||
|
||||||
|
message.includes('timeout') ||
|
||||||
|
message.includes('abort')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -23,6 +23,15 @@ import {
|
|||||||
getDeviceKeys,
|
getDeviceKeys,
|
||||||
deleteDeviceKeys,
|
deleteDeviceKeys,
|
||||||
} from './secure-storage';
|
} from './secure-storage';
|
||||||
|
import {
|
||||||
|
getQuickConfigFallback,
|
||||||
|
getWorkspaceInfoFallback,
|
||||||
|
getUsageStatsFallback,
|
||||||
|
getPluginStatusFallback,
|
||||||
|
getScheduledTasksFallback,
|
||||||
|
getSecurityStatusFallback,
|
||||||
|
isNotFoundError,
|
||||||
|
} from './api-fallbacks';
|
||||||
|
|
||||||
// === WSS Configuration ===
|
// === WSS Configuration ===
|
||||||
|
|
||||||
@@ -379,6 +388,14 @@ export class GatewayClient {
|
|||||||
private reconnectInterval: number;
|
private reconnectInterval: number;
|
||||||
private requestTimeout: number;
|
private requestTimeout: number;
|
||||||
|
|
||||||
|
// Heartbeat
|
||||||
|
private heartbeatInterval: number | null = null;
|
||||||
|
private heartbeatTimeout: number | null = null;
|
||||||
|
private missedHeartbeats: number = 0;
|
||||||
|
private static readonly HEARTBEAT_INTERVAL = 30000; // 30 seconds
|
||||||
|
private static readonly HEARTBEAT_TIMEOUT = 10000; // 10 seconds
|
||||||
|
private static readonly MAX_MISSED_HEARTBEATS = 3;
|
||||||
|
|
||||||
// State change callbacks
|
// State change callbacks
|
||||||
onStateChange?: (state: ConnectionState) => void;
|
onStateChange?: (state: ConnectionState) => void;
|
||||||
onLog?: (level: string, message: string) => void;
|
onLog?: (level: string, message: string) => void;
|
||||||
@@ -441,6 +458,7 @@ export class GatewayClient {
|
|||||||
if (health.status === 'ok') {
|
if (health.status === 'ok') {
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.setState('connected');
|
this.setState('connected');
|
||||||
|
this.startHeartbeat(); // Start heartbeat after successful connection
|
||||||
this.log('info', `Connected to OpenFang via REST API${health.version ? ` (v${health.version})` : ''}`);
|
this.log('info', `Connected to OpenFang via REST API${health.version ? ` (v${health.version})` : ''}`);
|
||||||
this.emitEvent('connected', { version: health.version });
|
this.emitEvent('connected', { version: health.version });
|
||||||
} else {
|
} else {
|
||||||
@@ -853,7 +871,10 @@ export class GatewayClient {
|
|||||||
const baseUrl = this.getRestBaseUrl();
|
const baseUrl = this.getRestBaseUrl();
|
||||||
const response = await fetch(`${baseUrl}${path}`);
|
const response = await fetch(`${baseUrl}${path}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`REST API error: ${response.status} ${response.statusText}`);
|
// For 404 errors, throw with status code so callers can handle gracefully
|
||||||
|
const error = new Error(`REST API error: ${response.status} ${response.statusText}`);
|
||||||
|
(error as any).status = response.status;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -934,19 +955,68 @@ export class GatewayClient {
|
|||||||
return this.restDelete(`/api/agents/${id}`);
|
return this.restDelete(`/api/agents/${id}`);
|
||||||
}
|
}
|
||||||
async getUsageStats(): Promise<any> {
|
async getUsageStats(): Promise<any> {
|
||||||
return this.restGet('/api/stats/usage');
|
try {
|
||||||
|
return await this.restGet('/api/stats/usage');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
return getUsageStatsFallback([]);
|
||||||
|
}
|
||||||
|
// Return minimal stats for other errors
|
||||||
|
return {
|
||||||
|
totalMessages: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
sessionsCount: 0,
|
||||||
|
agentsCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getSessionStats(): Promise<any> {
|
async getSessionStats(): Promise<any> {
|
||||||
return this.restGet('/api/stats/sessions');
|
try {
|
||||||
|
return await this.restGet('/api/stats/sessions');
|
||||||
|
} catch {
|
||||||
|
return { sessions: [] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getWorkspaceInfo(): Promise<any> {
|
async getWorkspaceInfo(): Promise<any> {
|
||||||
return this.restGet('/api/workspace');
|
try {
|
||||||
|
return await this.restGet('/api/workspace');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
return getWorkspaceInfoFallback();
|
||||||
|
}
|
||||||
|
// Return minimal info for other errors
|
||||||
|
return {
|
||||||
|
rootDir: process.env.HOME || process.env.USERPROFILE || '~',
|
||||||
|
skillsDir: null,
|
||||||
|
handsDir: null,
|
||||||
|
configDir: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getPluginStatus(): Promise<any> {
|
async getPluginStatus(): Promise<any> {
|
||||||
return this.restGet('/api/plugins/status');
|
try {
|
||||||
|
return await this.restGet('/api/plugins/status');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
const plugins = getPluginStatusFallback([]);
|
||||||
|
return { plugins, loaded: plugins.length, total: plugins.length };
|
||||||
|
}
|
||||||
|
return { plugins: [], loaded: 0, total: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getQuickConfig(): Promise<any> {
|
async getQuickConfig(): Promise<any> {
|
||||||
return this.restGet('/api/config/quick');
|
try {
|
||||||
|
return await this.restGet('/api/config/quick');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
return { quickConfig: getQuickConfigFallback() };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async saveQuickConfig(config: Record<string, any>): Promise<any> {
|
async saveQuickConfig(config: Record<string, any>): Promise<any> {
|
||||||
return this.restPut('/api/config/quick', config);
|
return this.restPut('/api/config/quick', config);
|
||||||
@@ -1006,7 +1076,17 @@ export class GatewayClient {
|
|||||||
return this.restGet('/api/channels/feishu/status');
|
return this.restGet('/api/channels/feishu/status');
|
||||||
}
|
}
|
||||||
async listScheduledTasks(): Promise<any> {
|
async listScheduledTasks(): Promise<any> {
|
||||||
return this.restGet('/api/scheduler/tasks');
|
try {
|
||||||
|
return await this.restGet('/api/scheduler/tasks');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
const tasks = getScheduledTasksFallback([]);
|
||||||
|
return { tasks, total: tasks.length };
|
||||||
|
}
|
||||||
|
// Return empty tasks list for other errors
|
||||||
|
return { tasks: [], total: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a scheduled task */
|
/** Create a scheduled task */
|
||||||
@@ -1325,12 +1405,32 @@ export class GatewayClient {
|
|||||||
|
|
||||||
/** Get security status */
|
/** Get security status */
|
||||||
async getSecurityStatus(): Promise<{ layers: { name: string; enabled: boolean }[] }> {
|
async getSecurityStatus(): Promise<{ layers: { name: string; enabled: boolean }[] }> {
|
||||||
return this.restGet('/api/security/status');
|
try {
|
||||||
|
return await this.restGet('/api/security/status');
|
||||||
|
} catch (error) {
|
||||||
|
// Return structured fallback if API not available (404)
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
const status = getSecurityStatusFallback();
|
||||||
|
return { layers: status.layers };
|
||||||
|
}
|
||||||
|
// Return minimal security layers for other errors
|
||||||
|
return {
|
||||||
|
layers: [
|
||||||
|
{ name: 'device_auth', enabled: true },
|
||||||
|
{ name: 'rbac', enabled: true },
|
||||||
|
{ name: 'audit_log', enabled: true },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get capabilities (RBAC) */
|
/** Get capabilities (RBAC) */
|
||||||
async getCapabilities(): Promise<{ capabilities: string[] }> {
|
async getCapabilities(): Promise<{ capabilities: string[] }> {
|
||||||
return this.restGet('/api/capabilities');
|
try {
|
||||||
|
return await this.restGet('/api/capabilities');
|
||||||
|
} catch {
|
||||||
|
return { capabilities: ['chat', 'agents', 'hands', 'workflows'] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === OpenFang Approvals API ===
|
// === OpenFang Approvals API ===
|
||||||
@@ -1402,6 +1502,12 @@ export class GatewayClient {
|
|||||||
// === Internal ===
|
// === Internal ===
|
||||||
|
|
||||||
private handleFrame(frame: GatewayFrame, connectResolve?: () => void, connectReject?: (error: Error) => void) {
|
private handleFrame(frame: GatewayFrame, connectResolve?: () => void, connectReject?: (error: Error) => void) {
|
||||||
|
// Handle pong responses for heartbeat
|
||||||
|
if (frame.type === 'pong') {
|
||||||
|
this.handlePong();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (frame.type === 'event') {
|
if (frame.type === 'event') {
|
||||||
this.handleEvent(frame, connectResolve, connectReject);
|
this.handleEvent(frame, connectResolve, connectReject);
|
||||||
} else if (frame.type === 'res') {
|
} else if (frame.type === 'res') {
|
||||||
@@ -1493,6 +1599,7 @@ export class GatewayClient {
|
|||||||
if (frame.ok) {
|
if (frame.ok) {
|
||||||
this.setState('connected');
|
this.setState('connected');
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
|
this.startHeartbeat(); // Start heartbeat after successful connection
|
||||||
this.emitEvent('connected', frame.payload);
|
this.emitEvent('connected', frame.payload);
|
||||||
this.log('info', 'Connected to Gateway');
|
this.log('info', 'Connected to Gateway');
|
||||||
connectResolve?.();
|
connectResolve?.();
|
||||||
@@ -1570,6 +1677,9 @@ export class GatewayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private cleanup() {
|
private cleanup() {
|
||||||
|
// Stop heartbeat on cleanup
|
||||||
|
this.stopHeartbeat();
|
||||||
|
|
||||||
for (const [, pending] of this.pendingRequests) {
|
for (const [, pending] of this.pendingRequests) {
|
||||||
clearTimeout(pending.timer);
|
clearTimeout(pending.timer);
|
||||||
pending.reject(new Error('Connection closed'));
|
pending.reject(new Error('Connection closed'));
|
||||||
@@ -1590,6 +1700,83 @@ export class GatewayClient {
|
|||||||
this.setState('disconnected');
|
this.setState('disconnected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Heartbeat Methods ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start heartbeat to keep connection alive.
|
||||||
|
* Called after successful connection.
|
||||||
|
*/
|
||||||
|
private startHeartbeat(): void {
|
||||||
|
this.stopHeartbeat();
|
||||||
|
this.missedHeartbeats = 0;
|
||||||
|
|
||||||
|
this.heartbeatInterval = window.setInterval(() => {
|
||||||
|
this.sendHeartbeat();
|
||||||
|
}, GatewayClient.HEARTBEAT_INTERVAL);
|
||||||
|
|
||||||
|
this.log('debug', 'Heartbeat started');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop heartbeat.
|
||||||
|
* Called on cleanup or disconnect.
|
||||||
|
*/
|
||||||
|
private stopHeartbeat(): void {
|
||||||
|
if (this.heartbeatInterval) {
|
||||||
|
clearInterval(this.heartbeatInterval);
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
}
|
||||||
|
if (this.heartbeatTimeout) {
|
||||||
|
clearTimeout(this.heartbeatTimeout);
|
||||||
|
this.heartbeatTimeout = null;
|
||||||
|
}
|
||||||
|
this.log('debug', 'Heartbeat stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a ping heartbeat to the server.
|
||||||
|
*/
|
||||||
|
private sendHeartbeat(): void {
|
||||||
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
||||||
|
this.log('debug', 'Skipping heartbeat - WebSocket not open');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.missedHeartbeats++;
|
||||||
|
if (this.missedHeartbeats > GatewayClient.MAX_MISSED_HEARTBEATS) {
|
||||||
|
this.log('warn', `Max missed heartbeats (${GatewayClient.MAX_MISSED_HEARTBEATS}), reconnecting`);
|
||||||
|
this.stopHeartbeat();
|
||||||
|
this.ws.close(4000, 'Heartbeat timeout');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send ping frame
|
||||||
|
try {
|
||||||
|
this.ws.send(JSON.stringify({ type: 'ping' }));
|
||||||
|
this.log('debug', `Ping sent (missed: ${this.missedHeartbeats})`);
|
||||||
|
|
||||||
|
// Set timeout for pong
|
||||||
|
this.heartbeatTimeout = window.setTimeout(() => {
|
||||||
|
this.log('warn', 'Heartbeat pong timeout');
|
||||||
|
// Don't reconnect immediately, let the next heartbeat check
|
||||||
|
}, GatewayClient.HEARTBEAT_TIMEOUT);
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to send heartbeat', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle pong response from server.
|
||||||
|
*/
|
||||||
|
private handlePong(): void {
|
||||||
|
this.missedHeartbeats = 0;
|
||||||
|
if (this.heartbeatTimeout) {
|
||||||
|
clearTimeout(this.heartbeatTimeout);
|
||||||
|
this.heartbeatTimeout = null;
|
||||||
|
}
|
||||||
|
this.log('debug', 'Pong received, heartbeat reset');
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly MAX_RECONNECT_ATTEMPTS = 10;
|
private static readonly MAX_RECONNECT_ATTEMPTS = 10;
|
||||||
|
|
||||||
private scheduleReconnect() {
|
private scheduleReconnect() {
|
||||||
@@ -1609,6 +1796,13 @@ export class GatewayClient {
|
|||||||
|
|
||||||
this.log('info', `Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
this.log('info', `Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
||||||
|
|
||||||
|
// Emit reconnecting event for UI
|
||||||
|
this.emitEvent('reconnecting', {
|
||||||
|
attempt: this.reconnectAttempts,
|
||||||
|
delay,
|
||||||
|
maxAttempts: GatewayClient.MAX_RECONNECT_ATTEMPTS
|
||||||
|
});
|
||||||
|
|
||||||
this.reconnectTimer = window.setTimeout(async () => {
|
this.reconnectTimer = window.setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
|
|||||||
Reference in New Issue
Block a user