Skip to content

Commit 108ba60

Browse files
committed
refactor: replace direct client instantiation with shared client in multiple modules
1 parent d81b2ae commit 108ba60

File tree

6 files changed

+71
-34
lines changed

6 files changed

+71
-34
lines changed

apps/desktop/src-tauri/src/captions.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use tokio::sync::Mutex;
1919
use tracing::instrument;
2020
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
2121

22+
use crate::shared_client::get_shared_client;
23+
2224
// Re-export caption types from cap_project
2325
pub use cap_project::{CaptionSegment, CaptionSettings};
2426

@@ -1067,7 +1069,7 @@ pub async fn download_whisper_model(
10671069
};
10681070

10691071
// Create the client and download the model
1070-
let client = Client::new();
1072+
let client = get_shared_client();
10711073
let response = client
10721074
.get(model_url)
10731075
.send()

apps/desktop/src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod posthog;
2121
mod presets;
2222
mod recording;
2323
mod recording_settings;
24+
mod shared_client;
2425
mod target_select_overlay;
2526
mod thumbnails;
2627
mod tray;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use reqwest::Client;
2+
use std::sync::OnceLock;
3+
4+
/// Global shared HTTP client instance
5+
static SHARED_CLIENT: OnceLock<Client> = OnceLock::new();
6+
7+
/// Get the shared HTTP client instance
8+
///
9+
/// This client is configured with retry policies and is shared across the entire application.
10+
/// This allows for global tracking of requests to each domain for DOS protection.
11+
pub fn get_shared_client() -> &'static Client {
12+
SHARED_CLIENT.get_or_init(|| {
13+
Client::builder()
14+
.timeout(std::time::Duration::from_secs(30))
15+
.build()
16+
.expect("Failed to create shared HTTP client")
17+
})
18+
}
19+
20+
/// Get a retryable client builder for specific hosts
21+
///
22+
/// This function creates a client builder with retry policies configured for the given host.
23+
/// The retry policies are designed to handle server errors and network issues while providing
24+
/// DOS protection through global request tracking.
25+
pub fn get_retryable_client_builder(host: String) -> reqwest::ClientBuilder {
26+
reqwest::Client::builder()
27+
.retry(
28+
reqwest::retry::for_host(host)
29+
.classify_fn(|req_rep| {
30+
match req_rep.status() {
31+
// Server errors and rate limiting
32+
Some(s) if s.is_server_error() || s == reqwest::StatusCode::TOO_MANY_REQUESTS => {
33+
req_rep.retryable()
34+
}
35+
// Network errors
36+
None => req_rep.retryable(),
37+
_ => req_rep.success(),
38+
}
39+
})
40+
.max_retries_per_request(5)
41+
.max_extra_load(5.0),
42+
)
43+
.timeout(std::time::Duration::from_secs(30))
44+
}
45+
46+
/// Get a retryable client for specific hosts
47+
///
48+
/// This function creates a client with retry policies configured for the given host.
49+
/// It's a convenience function that builds the client immediately.
50+
pub fn get_retryable_client(host: String) -> Result<Client, reqwest::Error> {
51+
get_retryable_client_builder(host).build()
52+
}

apps/desktop/src-tauri/src/upload.rs

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
UploadProgress, VideoUploadInfo,
55
api::{self, PresignedS3PutRequest, PresignedS3PutRequestMethod, S3VideoMeta, UploadedPart},
66
posthog::{PostHogEvent, async_capture_event},
7+
shared_client::{get_retryable_client, get_retryable_client_builder},
78
web_api::{AuthedApiError, ManagerExt},
89
};
910
use async_stream::{stream, try_stream};
@@ -596,24 +597,6 @@ pub fn from_pending_file_to_chunks(
596597
.instrument(Span::current())
597598
}
598599

599-
fn retryable_client(host: String) -> reqwest::ClientBuilder {
600-
reqwest::Client::builder().retry(
601-
reqwest::retry::for_host(host)
602-
.classify_fn(|req_rep| {
603-
match req_rep.status() {
604-
// Server errors
605-
Some(s) if s.is_server_error() || s == StatusCode::TOO_MANY_REQUESTS => {
606-
req_rep.retryable()
607-
}
608-
// Network errors
609-
None => req_rep.retryable(),
610-
_ => req_rep.success(),
611-
}
612-
})
613-
.max_retries_per_request(5)
614-
.max_extra_load(5.0),
615-
)
616-
}
617600

618601
/// Takes an incoming stream of bytes and individually uploads them to S3.
619602
///
@@ -730,8 +713,7 @@ fn multipart_uploader(
730713
format!("uploader/part/{part_number}/invalid_url: {err:?}")
731714
})?;
732715
let mut req =
733-
retryable_client(url.host().unwrap_or("<unknown>").to_string())
734-
.build()
716+
get_retryable_client(url.host().unwrap_or("<unknown>").to_string())
735717
.map_err(|err| {
736718
format!("uploader/part/{part_number}/client: {err:?}")
737719
})?
@@ -813,8 +795,7 @@ pub async fn singlepart_uploader(
813795

814796
let url = Uri::from_str(&presigned_url)
815797
.map_err(|err| format!("singlepart_uploader/invalid_url: {err:?}"))?;
816-
let resp = retryable_client(url.host().unwrap_or("<unknown>").to_string())
817-
.build()
798+
let resp = get_retryable_client(url.host().unwrap_or("<unknown>").to_string())
818799
.map_err(|err| format!("singlepart_uploader/client: {err:?}"))?
819800
.put(&presigned_url)
820801
.header("Content-Length", total_size)

apps/desktop/src-tauri/src/web_api.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use tracing::{error, warn};
66
use crate::{
77
ArcLock,
88
auth::{AuthSecret, AuthStore},
9+
shared_client::get_shared_client,
910
};
1011

1112
#[derive(Error, Debug)]
@@ -59,10 +60,10 @@ fn apply_env_headers(req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
5960

6061
async fn do_authed_request(
6162
auth: &AuthStore,
62-
build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder,
63+
build: impl FnOnce(&reqwest::Client, String) -> reqwest::RequestBuilder,
6364
url: String,
6465
) -> Result<reqwest::Response, reqwest::Error> {
65-
let client = reqwest::Client::new();
66+
let client = get_shared_client();
6667

6768
let req = build(client, url).header(
6869
"Authorization",
@@ -82,13 +83,13 @@ pub trait ManagerExt<R: Runtime>: Manager<R> {
8283
async fn authed_api_request(
8384
&self,
8485
path: impl Into<String>,
85-
build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder,
86+
build: impl FnOnce(&reqwest::Client, String) -> reqwest::RequestBuilder,
8687
) -> Result<reqwest::Response, AuthedApiError>;
8788

8889
async fn api_request(
8990
&self,
9091
path: impl Into<String>,
91-
build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder,
92+
build: impl FnOnce(&reqwest::Client, String) -> reqwest::RequestBuilder,
9293
) -> Result<reqwest::Response, reqwest::Error>;
9394

9495
async fn make_app_url(&self, pathname: impl AsRef<str>) -> String;
@@ -100,7 +101,7 @@ impl<T: Manager<R> + Emitter<R>, R: Runtime> ManagerExt<R> for T {
100101
async fn authed_api_request(
101102
&self,
102103
path: impl Into<String>,
103-
build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder,
104+
build: impl FnOnce(&reqwest::Client, String) -> reqwest::RequestBuilder,
104105
) -> Result<reqwest::Response, AuthedApiError> {
105106
let Some(auth) = AuthStore::get(self.app_handle()).map_err(AuthedApiError::AuthStore)?
106107
else {
@@ -122,10 +123,10 @@ impl<T: Manager<R> + Emitter<R>, R: Runtime> ManagerExt<R> for T {
122123
async fn api_request(
123124
&self,
124125
path: impl Into<String>,
125-
build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder,
126+
build: impl FnOnce(&reqwest::Client, String) -> reqwest::RequestBuilder,
126127
) -> Result<reqwest::Response, reqwest::Error> {
127128
let url = self.make_app_url(path.into()).await;
128-
let client = reqwest::Client::new();
129+
let client = get_shared_client();
129130

130131
apply_env_headers(build(client, url)).send().await
131132
}

packages/ui-solid/src/auto-imports.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ declare global {
2222
const IconCapCorners: typeof import('~icons/cap/corners.jsx')['default']
2323
const IconCapCrop: typeof import('~icons/cap/crop.jsx')['default']
2424
const IconCapCursor: typeof import('~icons/cap/cursor.jsx')['default']
25-
const IconCapEditor: typeof import("~icons/cap/editor.jsx")["default"]
25+
const IconCapEditor: typeof import('~icons/cap/editor.jsx')['default']
2626
const IconCapEnlarge: typeof import('~icons/cap/enlarge.jsx')['default']
2727
const IconCapFile: typeof import('~icons/cap/file.jsx')['default']
2828
const IconCapFilmCut: typeof import('~icons/cap/film-cut.jsx')['default']
@@ -57,7 +57,7 @@ declare global {
5757
const IconCapStopCircle: typeof import('~icons/cap/stop-circle.jsx')['default']
5858
const IconCapTrash: typeof import('~icons/cap/trash.jsx')['default']
5959
const IconCapUndo: typeof import('~icons/cap/undo.jsx')['default']
60-
const IconCapUpload: typeof import("~icons/cap/upload.jsx")["default"]
60+
const IconCapUpload: typeof import('~icons/cap/upload.jsx')['default']
6161
const IconCapX: typeof import('~icons/cap/x.jsx')['default']
6262
const IconCapZoomIn: typeof import('~icons/cap/zoom-in.jsx')['default']
6363
const IconCapZoomOut: typeof import('~icons/cap/zoom-out.jsx')['default']
@@ -69,7 +69,7 @@ declare global {
6969
const IconLucideClock: typeof import('~icons/lucide/clock.jsx')['default']
7070
const IconLucideDatabase: typeof import('~icons/lucide/database.jsx')['default']
7171
const IconLucideEdit: typeof import('~icons/lucide/edit.jsx')['default']
72-
const IconLucideEye: typeof import("~icons/lucide/eye.jsx")["default"]
72+
const IconLucideEye: typeof import('~icons/lucide/eye.jsx')['default']
7373
const IconLucideEyeOff: typeof import('~icons/lucide/eye-off.jsx')['default']
7474
const IconLucideFolder: typeof import('~icons/lucide/folder.jsx')['default']
7575
const IconLucideGift: typeof import('~icons/lucide/gift.jsx')['default']
@@ -88,7 +88,7 @@ declare global {
8888
const IconLucideUnplug: typeof import('~icons/lucide/unplug.jsx')['default']
8989
const IconLucideVideo: typeof import('~icons/lucide/video.jsx')['default']
9090
const IconLucideVolume2: typeof import('~icons/lucide/volume2.jsx')['default']
91-
const IconLucideVolumeX: typeof import("~icons/lucide/volume-x.jsx")["default"]
91+
const IconLucideVolumeX: typeof import('~icons/lucide/volume-x.jsx')['default']
9292
const IconMaterialSymbolsScreenshotFrame2Rounded: typeof import('~icons/material-symbols/screenshot-frame2-rounded.jsx')['default']
9393
const IconMdiLoading: typeof import("~icons/mdi/loading.jsx")["default"]
9494
const IconMdiMonitor: typeof import('~icons/mdi/monitor.jsx')['default']

0 commit comments

Comments
 (0)