Skip to content

Commit 7840edf

Browse files
committed
feat: server checking system rewrite & sentry integration
1 parent ca600de commit 7840edf

File tree

7 files changed

+165
-96
lines changed

7 files changed

+165
-96
lines changed

package-lock.json

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@guolao/vue-monaco-editor": "1.6.0",
19+
"@sentry/vue": "^10.30.0",
1920
"@tauri-apps/api": "2.9.1",
2021
"@tauri-apps/plugin-dialog": "2.4.2",
2122
"@tauri-apps/plugin-fs": "2.4.4",

src-tauri/src/commands/clients.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ pub fn initialize_rpc() -> Result<(), String> {
8282
}
8383

8484
#[tauri::command]
85-
pub fn get_server_connectivity_status() -> ServerConnectivityStatus {
85+
pub async fn get_server_connectivity_status() -> ServerConnectivityStatus {
8686
let servers = &SERVERS;
87+
servers.wait_for_initial_check().await;
8788
servers.connectivity_status.lock().unwrap().clone()
8889
}
8990

src-tauri/src/core/network/servers.rs

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,16 @@ use crate::{
22
core::utils::globals::{AUTH_SERVERS, CDN_SERVERS},
33
log_info, log_warn,
44
};
5-
use backoff::{future::retry, ExponentialBackoff};
65
use reqwest::Client;
7-
use std::sync::atomic::{AtomicUsize, Ordering};
8-
use std::sync::{Arc, LazyLock, Mutex, RwLock};
9-
use std::time::{Duration, Instant};
6+
use std::sync::{LazyLock, Mutex, RwLock};
7+
use std::time::Duration;
8+
use tokio::sync::watch;
109

11-
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
12-
const CB_MAX_FAILURES: usize = 3;
13-
const CB_RESET_WINDOW: Duration = Duration::from_secs(60);
14-
const BACKOFF_MAX_ELAPSED: Duration = Duration::from_secs(10);
15-
16-
#[derive(Debug)]
17-
pub struct CircuitBreaker {
18-
failures: AtomicUsize,
19-
last_failure: Mutex<Option<Instant>>,
20-
}
21-
22-
impl CircuitBreaker {
23-
fn new() -> Self {
24-
Self {
25-
failures: AtomicUsize::new(0),
26-
last_failure: Mutex::new(None),
27-
}
28-
}
29-
30-
fn record_failure(&self) {
31-
self.failures.fetch_add(1, Ordering::SeqCst);
32-
let mut last = self.last_failure.lock().unwrap();
33-
*last = Some(Instant::now());
34-
}
35-
36-
fn record_success(&self) {
37-
self.failures.store(0, Ordering::SeqCst);
38-
}
39-
40-
fn is_open(&self) -> bool {
41-
if self.failures.load(Ordering::SeqCst) < CB_MAX_FAILURES {
42-
return false;
43-
}
44-
let last = self.last_failure.lock().unwrap();
45-
if let Some(time) = *last {
46-
if time.elapsed() < CB_RESET_WINDOW {
47-
return true;
48-
}
49-
}
50-
false
51-
}
52-
}
10+
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
5311

5412
#[derive(Debug, Clone, serde::Serialize)]
5513
pub struct Server {
5614
pub url: String,
57-
#[serde(skip)]
58-
pub circuit_breaker: Arc<CircuitBreaker>,
5915
}
6016

6117
#[derive(Debug, Clone, serde::Serialize)]
@@ -71,13 +27,14 @@ pub struct Servers {
7127
pub selected_cdn: RwLock<Option<Server>>,
7228
pub selected_auth: RwLock<Option<Server>>,
7329
pub connectivity_status: Mutex<ServerConnectivityStatus>,
30+
pub check_complete_tx: watch::Sender<bool>,
31+
pub check_complete_rx: watch::Receiver<bool>,
7432
}
7533

7634
impl Server {
7735
pub fn new(url: &str) -> Self {
7836
Self {
7937
url: url.to_string(),
80-
circuit_breaker: Arc::new(CircuitBreaker::new()),
8138
}
8239
}
8340
}
@@ -99,6 +56,8 @@ impl Servers {
9956
None
10057
};
10158

59+
let (tx, rx) = watch::channel(false);
60+
10261
Self {
10362
cdns,
10463
auths,
@@ -108,6 +67,8 @@ impl Servers {
10867
cdn_online: false,
10968
auth_online: false,
11069
}),
70+
check_complete_tx: tx,
71+
check_complete_rx: rx,
11172
}
11273
}
11374

@@ -123,6 +84,15 @@ impl Servers {
12384
.await;
12485

12586
self.set_status();
87+
let _ = self.check_complete_tx.send(true);
88+
}
89+
90+
pub async fn wait_for_initial_check(&self) {
91+
let mut rx = self.check_complete_rx.clone();
92+
if *rx.borrow_and_update() {
93+
return;
94+
}
95+
let _ = rx.changed().await;
12696
}
12797

12898
async fn check_group(
@@ -133,51 +103,33 @@ impl Servers {
133103
name: &str,
134104
) {
135105
for server in servers {
136-
if server.circuit_breaker.is_open() {
137-
log_warn!("Skipping {} Server {}", name, server.url);
138-
continue;
139-
}
140-
141-
let op = || async {
142-
let resp = client.head(&server.url).send().await.map_err(|e| {
143-
backoff::Error::<Box<dyn std::error::Error + Send + Sync>>::transient(Box::new(
144-
e,
145-
))
146-
})?;
147-
if !resp.status().is_success() {
148-
return Err(
149-
backoff::Error::<Box<dyn std::error::Error + Send + Sync>>::transient(
150-
format!("Status not success: {}", resp.status()).into(),
151-
),
152-
);
153-
}
154-
Ok(resp)
155-
};
156-
157-
let backoff = ExponentialBackoff {
158-
max_elapsed_time: Some(BACKOFF_MAX_ELAPSED),
159-
..Default::default()
160-
};
161-
162-
match retry(backoff, op).await {
163-
Ok(response) => {
164-
log_info!(
165-
"{} Server {} responded with: {}",
166-
name,
167-
server.url,
168-
response.status()
169-
);
170-
server.circuit_breaker.record_success();
171-
let mut lock = selected.write().unwrap();
172-
*lock = Some(server.clone());
173-
return;
106+
match client.head(&server.url).send().await {
107+
Ok(resp) => {
108+
if resp.status().is_success() {
109+
log_info!(
110+
"{} Server {} responded with: {}",
111+
name,
112+
server.url,
113+
resp.status()
114+
);
115+
let mut lock = selected.write().unwrap();
116+
*lock = Some(server.clone());
117+
return;
118+
} else {
119+
log_warn!(
120+
"{} Server {} returned status: {}",
121+
name,
122+
server.url,
123+
resp.status()
124+
);
125+
}
174126
}
175127
Err(e) => {
176128
log_warn!("Failed to connect to {} Server {}: {}", name, server.url, e);
177-
server.circuit_breaker.record_failure();
178129
}
179130
}
180131
}
132+
181133
let mut lock = selected.write().unwrap();
182134
*lock = None;
183135
}

src/components/features/social/InlineIRCChat.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
2-
<div class="irc-chat flex flex-col bg-base-200 border border-base-300 rounded-lg overflow-hidden transition-all duration-300 ease-out relative mb-6"
2+
<div v-bind="attrs"
3+
class="irc-chat flex flex-col bg-base-200 border border-base-300 rounded-lg overflow-hidden transition-all duration-300 ease-out relative mb-6"
34
:class="isExpanded ? 'shadow-lg max-h-[380px]' : 'shadow-sm max-h-[68px] hover:shadow-md'">
45
<button type="button" class="flex items-center justify-between w-full px-4 py-3 bg-base-300/40 cursor-pointer"
56
@click="toggleExpanded">
@@ -80,12 +81,14 @@
8081
</template>
8182

8283
<script setup lang="ts">
83-
import { computed, nextTick, onMounted, ref, watch, onUnmounted } from 'vue';
84+
import { computed, nextTick, onMounted, ref, watch, onUnmounted, useAttrs } from 'vue';
85+
defineOptions({ inheritAttrs: false });
8486
import { CheckCircle2, ChevronDown, Loader2, MessageSquare, RefreshCw, WifiOff, AtSign, Copy } from 'lucide-vue-next';
8587
import { useToast } from '../../../services/toastService';
8688
import { useIrcChat } from '../../../composables/useIrcChat';
8789
import { useI18n } from 'vue-i18n';
8890
91+
const attrs = useAttrs();
8992
const { messages, connected, status, sendIrcMessage, forceReconnect, ensureIrcConnection } = useIrcChat();
9093
const { t } = useI18n();
9194
const inputMessage = ref('');

src/composables/useIrcChat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export const ensureIrcConnection = async (isReconnect = false): Promise<void> =>
156156
status.value = isReconnect ? 'reconnecting' : 'connecting';
157157
const token = localStorage.getItem('authToken') || '';
158158
const tokenPresent = token.length > 0;
159-
console.debug('IRC: attempting connect, token present=', tokenPresent);
159+
console.debug('IRC: attempting connect, token present =', tokenPresent);
160160

161161
clearReconnectTimer();
162162
await invoke('connect_irc', { token: tokenPresent ? token : null });

src/main.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,30 @@ import i18n from './i18n/index';
55
import { initializeAuthUrl } from './config';
66
import { loader } from '@guolao/vue-monaco-editor';
77

8+
import * as Sentry from "@sentry/vue";
9+
810
loader.config({
911
paths: {
1012
vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs',
1113
},
1214
})
1315

16+
const app = createApp(App)
17+
.use(Vue3Lottie)
18+
.use(i18n);
19+
1420
initializeAuthUrl()
1521
.finally(() => {
16-
createApp(App)
17-
.use(Vue3Lottie)
18-
.use(i18n)
19-
.mount('#app');
22+
app.mount('#app');
2023
});
24+
25+
Sentry.init({
26+
app,
27+
dsn: "https://2220bc70de22a1841c3792c4bf314731@o4510521933889536.ingest.de.sentry.io/4510521935528016",
28+
sendDefaultPii: true,
29+
integrations: [
30+
Sentry.replayIntegration()
31+
],
32+
replaysSessionSampleRate: 0.1,
33+
replaysOnErrorSampleRate: 1.0
34+
});

0 commit comments

Comments
 (0)