Skip to content

Commit ec22312

Browse files
committed
update files
1 parent df3e2ff commit ec22312

200 files changed

Lines changed: 10865 additions & 3680 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎desktop/src-tauri/src/browser_dock.rs‎

Lines changed: 165 additions & 56 deletions
Large diffs are not rendered by default.

‎desktop/src-tauri/src/fetch_worker.rs‎

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,11 @@ struct FetchResponse {
285285
#[derive(Clone)]
286286
pub struct TauriFetchController(Arc<TauriFetchControllerInner>);
287287

288+
const PENDING_REQUEST_MAX_AGE: Duration = Duration::from_secs(120);
289+
288290
struct TauriFetchControllerInner {
289291
app: AppHandle,
290-
pending: Mutex<HashMap<u64, oneshot::Sender<FetchResponse>>>,
292+
pending: Mutex<HashMap<u64, (std::time::Instant, oneshot::Sender<FetchResponse>)>>,
291293
chunks: Mutex<HashMap<u64, ChunkBuffer>>,
292294
nav_waiters: Mutex<Vec<oneshot::Sender<()>>>,
293295
drive_lock: AsyncMutex<()>,
@@ -311,6 +313,16 @@ impl TauriFetchController {
311313
self.0.chunks.lock().remove(&req_id);
312314
}
313315

316+
fn sweep_stale_requests(&self) {
317+
let mut pending = self.0.pending.lock();
318+
let before = pending.len();
319+
pending.retain(|_, (created_at, _)| created_at.elapsed() < PENDING_REQUEST_MAX_AGE);
320+
if pending.len() < before {
321+
let mut chunks = self.0.chunks.lock();
322+
chunks.retain(|req_id, _| pending.contains_key(req_id));
323+
}
324+
}
325+
314326
pub fn deliver_result(
315327
&self,
316328
req_id: u64,
@@ -320,7 +332,7 @@ impl TauriFetchController {
320332
) {
321333
self.0.chunks.lock().remove(&req_id);
322334
let sender = self.0.pending.lock().remove(&req_id);
323-
if let Some(tx) = sender {
335+
if let Some((_, tx)) = sender {
324336
let _ = tx.send(FetchResponse { ok, value, error });
325337
}
326338
}
@@ -356,13 +368,13 @@ impl TauriFetchController {
356368
.and_then(|v| v.as_str())
357369
.map(|s| s.to_string());
358370
let sender = self.0.pending.lock().remove(&req_id);
359-
if let Some(tx) = sender {
371+
if let Some((_, tx)) = sender {
360372
let _ = tx.send(FetchResponse { ok, value, error });
361373
}
362374
}
363375
Err(err) => {
364376
let sender = self.0.pending.lock().remove(&req_id);
365-
if let Some(tx) = sender {
377+
if let Some((_, tx)) = sender {
366378
let _ = tx.send(FetchResponse {
367379
ok: false,
368380
value: Value::Null,
@@ -437,9 +449,13 @@ impl TauriFetchController {
437449
.app
438450
.get_webview(FETCH_WORKER_LABEL)
439451
.ok_or_else(|| anyhow::anyhow!("fetch_worker webview not available"))?;
452+
self.sweep_stale_requests();
440453
let req_id = self.0.next_req_id.fetch_add(1, Ordering::SeqCst);
441454
let (tx, rx) = oneshot::channel();
442-
self.0.pending.lock().insert(req_id, tx);
455+
self.0
456+
.pending
457+
.lock()
458+
.insert(req_id, (std::time::Instant::now(), tx));
443459
let payload = serde_json::json!({
444460
"reqId": req_id,
445461
"kind": kind,
@@ -480,10 +496,33 @@ impl TauriFetchController {
480496
}
481497
}
482498

483-
#[async_trait]
484-
impl FetchController for TauriFetchController {
485-
async fn fetch(&self, url: &str, timeout: Duration) -> Result<FetchedPage> {
486-
let _drive = self.0.drive_lock.lock().await;
499+
impl TauriFetchController {
500+
fn park_worker_to_blank(&self) {
501+
let outcome = run_fetch_on_main_thread(
502+
&self.0.app,
503+
"fetch_worker park",
504+
move |app| {
505+
let Some(webview) = app.get_webview(FETCH_WORKER_LABEL) else {
506+
return Ok(());
507+
};
508+
if let Ok(current) = webview.url() {
509+
if current.as_str() == ABOUT_BLANK {
510+
return Ok(());
511+
}
512+
}
513+
let parsed = Url::parse(ABOUT_BLANK)
514+
.map_err(|e| anyhow::anyhow!("parse about:blank: {e}"))?;
515+
webview
516+
.navigate(parsed)
517+
.map_err(|e| anyhow::anyhow!("fetch_worker park navigate failed: {e}"))
518+
},
519+
);
520+
if let Err(err) = outcome {
521+
tracing::warn!("[fetch_worker] park to about:blank failed: {err}");
522+
}
523+
}
524+
525+
async fn fetch_inner(&self, url: &str, timeout: Duration) -> Result<FetchedPage> {
487526
ensure_fetch_webview(&self.0.app)?;
488527

489528
let state = self
@@ -506,7 +545,7 @@ impl FetchController for TauriFetchController {
506545
.map_err(|e| anyhow::anyhow!("fetch_worker navigate failed: {e}"))
507546
})?;
508547

509-
let nav_timeout = timeout.min(Duration::from_secs(45));
548+
let nav_timeout = timeout.min(Duration::from_secs(8));
510549
if let Err(err) = self
511550
.await_nav_ready(parsed.as_str(), nav_timeout)
512551
.await
@@ -518,18 +557,18 @@ impl FetchController for TauriFetchController {
518557
.exec_internal(
519558
"wait_for_ready",
520559
serde_json::json!({
521-
"ready_state": "complete",
522-
"timeout_ms": 10_000,
560+
"ready_state": "interactive",
561+
"timeout_ms": 4_000,
523562
}),
524-
Duration::from_millis(12_000),
563+
Duration::from_millis(5_000),
525564
)
526565
.await;
527566

528567
let resp = self
529568
.exec_internal(
530569
"extract_text",
531570
Value::Null,
532-
Duration::from_millis(10_000),
571+
Duration::from_millis(8_000),
533572
)
534573
.await?;
535574
if !resp.ok {
@@ -538,29 +577,56 @@ impl FetchController for TauriFetchController {
538577
resp.error.unwrap_or_default()
539578
));
540579
}
541-
let url_out = resp
542-
.value
580+
let mut page = page_from_extract_value(&resp.value);
581+
if page.text.chars().count() < 200 {
582+
tokio::time::sleep(Duration::from_millis(1_200)).await;
583+
if let Ok(retry) = self
584+
.exec_internal(
585+
"extract_text",
586+
Value::Null,
587+
Duration::from_millis(8_000),
588+
)
589+
.await
590+
{
591+
if retry.ok {
592+
let second = page_from_extract_value(&retry.value);
593+
if second.text.chars().count() > page.text.chars().count() {
594+
page = second;
595+
}
596+
}
597+
}
598+
}
599+
Ok(page)
600+
}
601+
}
602+
603+
fn page_from_extract_value(value: &Value) -> FetchedPage {
604+
FetchedPage {
605+
url: value
543606
.get("url")
544607
.and_then(|v| v.as_str())
545608
.unwrap_or("")
546-
.to_string();
547-
let title = resp
548-
.value
609+
.to_string(),
610+
title: value
549611
.get("title")
550612
.and_then(|v| v.as_str())
551613
.unwrap_or("")
552-
.to_string();
553-
let text = resp
554-
.value
614+
.to_string(),
615+
text: value
555616
.get("text")
556617
.and_then(|v| v.as_str())
557618
.unwrap_or("")
558-
.to_string();
559-
Ok(FetchedPage {
560-
url: url_out,
561-
title,
562-
text,
563-
})
619+
.to_string(),
620+
}
621+
}
622+
623+
#[async_trait]
624+
impl FetchController for TauriFetchController {
625+
async fn fetch(&self, url: &str, timeout: Duration) -> Result<FetchedPage> {
626+
let _drive = self.0.drive_lock.lock().await;
627+
let result = self.fetch_inner(url, timeout).await;
628+
self.park_worker_to_blank();
629+
result
564630
}
565631
}
566632

‎desktop/src-tauri/src/gateway_bridge.rs‎

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,32 +81,65 @@ pub fn spawn_bridge_client(gateway_url: String, token: String, generation: u64)
8181
});
8282
}
8383

84+
const BRIDGE_PING_INTERVAL: Duration = Duration::from_secs(30);
85+
86+
const BRIDGE_IDLE_TIMEOUT: Duration = Duration::from_secs(90);
87+
8488
async fn run_bridge_session(
8589
stream: tokio_tungstenite::WebSocketStream<
8690
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
8791
>,
8892
generation: u64,
8993
) {
9094
let (sink, mut reader) = stream.split();
91-
let (out_tx, mut out_rx) = tokio::sync::mpsc::channel::<String>(256);
95+
let (out_tx, mut out_rx) = tokio::sync::mpsc::channel::<Message>(256);
9296

9397
let writer = tauri::async_runtime::spawn(async move {
9498
let mut sink = sink;
9599
while let Some(frame) = out_rx.recv().await {
96-
if sink.send(Message::Text(frame.into())).await.is_err() {
100+
if sink.send(frame).await.is_err() {
97101
break;
98102
}
99103
}
104+
let _ = sink.close().await;
100105
});
101106

102107
let dispatch_gate = std::sync::Arc::new(tokio::sync::Semaphore::new(
103108
DISPATCH_MAX_CONCURRENCY,
104109
));
105110

106-
while let Some(message) = reader.next().await {
111+
let mut ping_interval = tokio::time::interval(BRIDGE_PING_INTERVAL);
112+
ping_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
113+
ping_interval.reset();
114+
let mut last_rx = std::time::Instant::now();
115+
116+
loop {
117+
let message = tokio::select! {
118+
message = reader.next() => {
119+
let Some(message) = message else { break };
120+
message
121+
}
122+
_ = ping_interval.tick() => {
123+
if current_bridge_generation() != generation {
124+
break;
125+
}
126+
if last_rx.elapsed() > BRIDGE_IDLE_TIMEOUT {
127+
tracing::warn!(
128+
"[gateway_bridge] no inbound traffic for {}s; dropping stale connection",
129+
BRIDGE_IDLE_TIMEOUT.as_secs()
130+
);
131+
break;
132+
}
133+
if out_tx.send(Message::Ping(Vec::new().into())).await.is_err() {
134+
break;
135+
}
136+
continue;
137+
}
138+
};
107139
if current_bridge_generation() != generation {
108140
break;
109141
}
142+
last_rx = std::time::Instant::now();
110143
let text = match message {
111144
Ok(Message::Text(t)) => t.to_string(),
112145
Ok(Message::Close(_)) | Err(_) => break,
@@ -135,7 +168,11 @@ async fn run_bridge_session(
135168
.map(ToString::to_string);
136169
let (Some(target), Some(method)) = (target, method) else {
137170
let response = json!({ "id": id, "ok": false, "error": "malformed frame" });
138-
if out_tx.send(response.to_string()).await.is_err() {
171+
if out_tx
172+
.send(Message::Text(response.to_string().into()))
173+
.await
174+
.is_err()
175+
{
139176
tracing::error!(
140177
"[gateway_bridge] failed to queue malformed-frame response for request {id}: outbound channel closed"
141178
);
@@ -164,15 +201,20 @@ async fn run_bridge_session(
164201
Ok(value) => json!({ "id": id, "ok": true, "value": value }),
165202
Err(err) => json!({ "id": id, "ok": false, "error": err }),
166203
};
167-
if out_tx.send(response.to_string()).await.is_err() {
204+
if out_tx
205+
.send(Message::Text(response.to_string().into()))
206+
.await
207+
.is_err()
208+
{
168209
tracing::error!(
169210
"[gateway_bridge] failed to queue bridge response for request {id}: outbound channel closed"
170211
);
171212
}
172213
});
173214
}
174215

175-
writer.abort();
216+
drop(out_tx);
217+
let _ = writer;
176218
}
177219

178220
async fn dispatch(target: &str, method: &str, args: Value) -> Result<Value, String> {

0 commit comments

Comments
 (0)