Skip to content

Commit 43206c2

Browse files
feat: add proxy settings and make BGM token optional (#60)
* feat: add skip version button * feat: add custom theme color * feat: add ImmersiveTitlebar * feat: simplify HTTP request instances * feat: add proxy settings * feat: bgm token not required * fix: clear import * feat: optimize style * Revert "feat: add ImmersiveTitlebar" This reverts commit c36c0f4. * Revert "feat: add custom theme color" This reverts commit a7a72e2. * fix: resolve conflicts and add missing translations after revert * refactor: simplify proxy config and make BGM token optional * refactor: remove requiresBgmToken and simplify withBgmAuth --------- Co-authored-by: huoshen80 <huoshen80@hotmail.com>
1 parent 2c40dff commit 43206c2

32 files changed

Lines changed: 412 additions & 228 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"@types/react-dom": "^18.3.7",
7272
"@vitejs/plugin-react": "^6.0.2",
7373
"git-cliff": "^2.13.1",
74-
"i18next-cli": "^1.61.0",
74+
"i18next-cli": "=1.58.3",
7575
"typescript": "^6.0.3",
7676
"unocss": "^66.7.0",
7777
"vite": "^8.0.16"

pnpm-lock.yaml

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

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use utils::{
1616
import_clipboard_image_to_temp, is_portable_mode, move_backup_folder, open_directory,
1717
},
1818
game_cover::{delete_cloud_cache, register_game_cover_protocol},
19+
http::update_proxy_config,
1920
launch::{launch_game, stop_game},
2021
legacy_migration::run_startup_migrations,
2122
logs::{get_reina_log_level, set_reina_log_level},
@@ -101,6 +102,7 @@ pub fn run() {
101102
// 用户设置相关 commands
102103
get_all_settings,
103104
update_settings,
105+
update_proxy_config,
104106
// BGM OAuth 相关 commands
105107
bgm_oauth_start_login,
106108
bgm_oauth_exchange_code,

src-tauri/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod bgm_auth;
55
pub mod fs;
66
pub mod game_cover;
77
pub mod game_monitor;
8+
pub mod http;
89
pub mod launch;
910
pub mod legacy_migration;
1011
pub mod logs;

src-tauri/src/utils/bgm_auth.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,10 @@ use chrono::Utc;
1111
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
1212
use serde::Deserialize;
1313
use tauri::{AppHandle, Emitter, State};
14-
use tauri_plugin_http::reqwest;
1514

1615
use crate::database::repository::settings_repository::SettingsRepository;
1716
use crate::entity::user::BgmAuth;
1817

19-
const USER_AGENT: &str = concat!(
20-
"huoshen80/ReinaManager/",
21-
env!("CARGO_PKG_VERSION"),
22-
" (https://github.com/huoshen80/ReinaManager)"
23-
);
2418
const BGM_APP_ID: &str = "bgm606669f8b19c14e6e";
2519
const BGM_REDIRECT_URI: &str = "http://127.0.0.1:23380/callback";
2620
const BGM_CALLBACK_PORT: u16 = 23380;
@@ -242,9 +236,8 @@ fn parse_callback(stream: &std::net::TcpStream) -> Option<(String, Option<String
242236
}
243237

244238
async fn request_token(body: &serde_json::Value) -> Result<BgmTokenResponse, String> {
245-
let response = reqwest::Client::new()
239+
let response = crate::utils::http::get_client()
246240
.post("https://bgm.tv/oauth/access_token")
247-
.header("User-Agent", USER_AGENT)
248241
.header("Content-Type", "application/json")
249242
.body(serde_json::to_vec(body).map_err(|e| format!("序列化请求体失败: {}", e))?)
250243
.send()

src-tauri/src/utils/game_cover.rs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use std::collections::{HashMap, HashSet};
22
use std::io::ErrorKind;
33
use std::path::{Path, PathBuf};
4-
use std::sync::{Arc, Mutex, OnceLock};
4+
use std::sync::{Arc, Mutex};
55
use std::time::{Duration, SystemTime, UNIX_EPOCH};
66

77
use sea_orm::{DatabaseConnection, EntityTrait};
88
use tauri::Manager;
99
use tauri::command;
1010
use tauri::http::{Response, StatusCode};
11-
use tauri_plugin_http::reqwest::Client;
1211
use tokio::sync::{RwLock, Semaphore, watch};
1312

1413
use crate::entity::prelude::Games;
@@ -17,18 +16,9 @@ use reina_path::get_base_data_dir;
1716
const DEFAULT_COVER_EXTENSION: &str = "jpg";
1817
const DEFAULT_CLOUD_COVER_FILE_NAME: &str = "cloud_cover";
1918
const MAX_CONCURRENT_COVER_DOWNLOADS: usize = 100;
20-
const COVER_DOWNLOAD_CONNECT_TIMEOUT_SECS: u64 = 10;
21-
const COVER_DOWNLOAD_TIMEOUT_SECS: u64 = 60;
2219
/// 最多重试次数(不含首次),退避延迟为 500ms * 2^attempt
2320
const COVER_MAX_RETRIES: u32 = 2;
2421
const COVER_RETRY_BASE_DELAY_MS: u64 = 500;
25-
const COVER_USER_AGENT: &str = concat!(
26-
"huoshen80/ReinaManager/",
27-
env!("CARGO_PKG_VERSION"),
28-
" (https://github.com/huoshen80/ReinaManager)"
29-
);
30-
31-
static COVER_HTTP_CLIENT: OnceLock<Client> = OnceLock::new();
3222

3323
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
3424
struct DownloadKey {
@@ -105,17 +95,6 @@ impl Drop for DownloadCleanupGuard {
10595
}
10696
}
10797

108-
fn cover_http_client() -> &'static Client {
109-
COVER_HTTP_CLIENT.get_or_init(|| {
110-
Client::builder()
111-
.connect_timeout(Duration::from_secs(COVER_DOWNLOAD_CONNECT_TIMEOUT_SECS))
112-
.timeout(Duration::from_secs(COVER_DOWNLOAD_TIMEOUT_SECS))
113-
.user_agent(COVER_USER_AGENT)
114-
.build()
115-
.expect("failed to build game cover http client")
116-
})
117-
}
118-
11998
fn infer_cache_extension(cloud_url: &str) -> String {
12099
let url_without_suffix = cloud_url.split(['?', '#']).next().unwrap_or(cloud_url);
121100
let file_name = url_without_suffix
@@ -344,7 +323,7 @@ async fn try_download_once(
344323
let cache_path = build_cache_path(game_cover_dir, game_id, &extension);
345324
let temp_path = build_temp_cache_path(game_cover_dir, game_id, &extension);
346325

347-
let response = cover_http_client()
326+
let response = crate::utils::http::get_client()
348327
.get(url)
349328
.send()
350329
.await

src-tauri/src/utils/http.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use serde::Deserialize;
2+
use std::sync::{OnceLock, RwLock};
3+
use std::time::Duration;
4+
use tauri_plugin_http::reqwest::{Client, NoProxy, Proxy};
5+
6+
const GLOBAL_USER_AGENT: &str = concat!(
7+
"huoshen80/ReinaManager/",
8+
env!("CARGO_PKG_VERSION"),
9+
" (https://github.com/huoshen80/ReinaManager)"
10+
);
11+
12+
const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 10;
13+
const DEFAULT_TIMEOUT_SECS: u64 = 60;
14+
const LOCAL_PROXY_BYPASS: &str = "localhost,127.0.0.0/8,::1,0.0.0.0,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,fc00::/7,fe80::/10,.local";
15+
16+
#[derive(Debug, Clone, Deserialize)]
17+
pub struct ProxyConfig {
18+
pub url: String,
19+
}
20+
21+
static GLOBAL_HTTP_CLIENT: OnceLock<RwLock<Client>> = OnceLock::new();
22+
23+
#[tauri::command]
24+
pub fn update_proxy_config(config: ProxyConfig) -> Result<(), String> {
25+
let client = build_client(config.url.trim())?;
26+
let mut guard = http_client()
27+
.write()
28+
.map_err(|_| "更新 HTTP 客户端失败".to_string())?;
29+
*guard = client;
30+
Ok(())
31+
}
32+
33+
fn build_client(proxy_url: &str) -> Result<Client, String> {
34+
let mut builder = Client::builder()
35+
.connect_timeout(Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS))
36+
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
37+
.user_agent(GLOBAL_USER_AGENT);
38+
39+
if !proxy_url.is_empty() {
40+
let proxy = Proxy::all(proxy_url)
41+
.map_err(|e| format!("代理地址无效: {e}"))?
42+
.no_proxy(NoProxy::from_string(LOCAL_PROXY_BYPASS));
43+
builder = builder.proxy(proxy);
44+
}
45+
46+
builder
47+
.build()
48+
.map_err(|e| format!("创建 HTTP 客户端失败: {e}"))
49+
}
50+
51+
fn http_client() -> &'static RwLock<Client> {
52+
GLOBAL_HTTP_CLIENT
53+
.get_or_init(|| RwLock::new(build_client("").expect("failed to build default http client")))
54+
}
55+
56+
pub fn get_client() -> Client {
57+
http_client()
58+
.read()
59+
.unwrap_or_else(|e| e.into_inner())
60+
.clone()
61+
}

src/api/bgm.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,16 @@ export interface BgmTokenStatus {
6868
scope: string | null;
6969
}
7070

71-
function buildBgmAuthHeaders(token: string) {
72-
return {
73-
headers: {
74-
...BGM_JSON_HEADERS,
75-
Authorization: `Bearer ${token}`,
76-
},
77-
};
71+
function buildBgmAuthHeaders(token?: string) {
72+
const headers: Record<string, string> = { ...BGM_JSON_HEADERS };
73+
if (token) {
74+
headers.Authorization = `Bearer ${token}`;
75+
}
76+
return { headers };
7877
}
7978

8079
function buildBgmRateLimitedOptions(
81-
token: string,
80+
token?: string,
8281
signal?: AbortSignal,
8382
): TauriHttpOptions {
8483
return {
@@ -173,7 +172,7 @@ const transformBgmData = (BGMdata: any): GameCandidateData => {
173172
*/
174173
export async function fetchBgmById(
175174
id: string,
176-
token: string,
175+
token?: string,
177176
signal?: AbortSignal,
178177
): Promise<GameCandidateData> {
179178
const BGMdata = (
@@ -203,7 +202,7 @@ export async function fetchBgmById(
203202
*/
204203
export async function fetchBgmByName(
205204
name: string,
206-
token: string,
205+
token?: string,
207206
limit = 25,
208207
signal?: AbortSignal,
209208
): Promise<GameCandidateData[]> {
@@ -246,7 +245,7 @@ export async function fetchBgmByName(
246245
*/
247246
export async function fetchBgmByIds(
248247
ids: string[],
249-
token: string,
248+
token?: string,
250249
signal?: AbortSignal,
251250
): Promise<GameCandidateData[]> {
252251
if (ids.length === 0) {

src/api/gameMetadataService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function createMetadataError(
5959
}
6060

6161
function createStableError(
62-
code: "bgm_token_required" | "invalid_game_id" | "unsupported_source",
62+
code: "invalid_game_id" | "unsupported_source",
6363
message: string,
6464
): AppError {
6565
return new AppError({

0 commit comments

Comments
 (0)