@bghitcode/bghitapp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -0
  3. package/dist/cli.js +2995 -0
  4. package/package.json +104 -0
  5. package/src-tauri/Cargo.lock +5966 -0
  6. package/src-tauri/Cargo.toml +59 -0
  7. package/src-tauri/Info.plist +14 -0
  8. package/src-tauri/assets/macos/dmg/background.png +0 -0
  9. package/src-tauri/assets/main.wxs +350 -0
  10. package/src-tauri/bghitapp.json +42 -0
  11. package/src-tauri/build.rs +5 -0
  12. package/src-tauri/capabilities/default.json +29 -0
  13. package/src-tauri/entitlements.plist +7 -0
  14. package/src-tauri/icons/chatgpt.icns +0 -0
  15. package/src-tauri/icons/deepseek.icns +0 -0
  16. package/src-tauri/icons/excalidraw.icns +0 -0
  17. package/src-tauri/icons/flomo.icns +0 -0
  18. package/src-tauri/icons/gemini.icns +0 -0
  19. package/src-tauri/icons/grok.icns +0 -0
  20. package/src-tauri/icons/icon.icns +0 -0
  21. package/src-tauri/icons/icon.png +0 -0
  22. package/src-tauri/icons/lizhi.icns +0 -0
  23. package/src-tauri/icons/programmusic.icns +0 -0
  24. package/src-tauri/icons/qwerty.icns +0 -0
  25. package/src-tauri/icons/twitter.icns +0 -0
  26. package/src-tauri/icons/wechat.icns +0 -0
  27. package/src-tauri/icons/weekly.icns +0 -0
  28. package/src-tauri/icons/weread.icns +0 -0
  29. package/src-tauri/icons/xiaohongshu.icns +0 -0
  30. package/src-tauri/icons/youtube.icns +0 -0
  31. package/src-tauri/icons/youtubemusic.icns +0 -0
  32. package/src-tauri/rust_proxy.toml +10 -0
  33. package/src-tauri/src/app/config.rs +100 -0
  34. package/src-tauri/src/app/invoke.rs +242 -0
  35. package/src-tauri/src/app/menu.rs +324 -0
  36. package/src-tauri/src/app/mod.rs +6 -0
  37. package/src-tauri/src/app/setup.rs +172 -0
  38. package/src-tauri/src/app/window.rs +577 -0
  39. package/src-tauri/src/inject/auth.js +75 -0
  40. package/src-tauri/src/inject/custom.js +0 -0
  41. package/src-tauri/src/inject/event.js +1111 -0
  42. package/src-tauri/src/inject/find.js +708 -0
  43. package/src-tauri/src/inject/fullscreen.js +253 -0
  44. package/src-tauri/src/inject/offline.js +68 -0
  45. package/src-tauri/src/inject/splash-transition.js +13 -0
  46. package/src-tauri/src/inject/style.js +505 -0
  47. package/src-tauri/src/inject/theme_refresh.js +59 -0
  48. package/src-tauri/src/inject/toast.js +22 -0
  49. package/src-tauri/src/lib.rs +227 -0
  50. package/src-tauri/src/main.rs +8 -0
  51. package/src-tauri/src/util.rs +245 -0
  52. package/src-tauri/tauri.conf.json +20 -0
  53. package/src-tauri/tauri.linux.conf.json +12 -0
  54. package/src-tauri/tauri.macos.conf.json +28 -0
  55. package/src-tauri/tauri.windows.conf.json +15 -0
@@ -0,0 +1,577 @@
1
+ use crate::app::config::BghitappConfig;
2
+ use crate::util::{
3
+ check_file_or_append, get_data_dir, get_download_message_with_lang, show_toast, MessageType,
4
+ };
5
+ use std::{
6
+ path::PathBuf,
7
+ str::FromStr,
8
+ sync::atomic::{AtomicU32, Ordering},
9
+ };
10
+ use tauri::{
11
+ webview::{DownloadEvent, NewWindowFeatures, NewWindowResponse},
12
+ AppHandle, Config, Manager, Url, WebviewUrl, WebviewWindow, WebviewWindowBuilder,
13
+ };
14
+
15
+ #[cfg(target_os = "macos")]
16
+ use tauri::{Theme, TitleBarStyle};
17
+
18
+ #[cfg(target_os = "windows")]
19
+ fn build_proxy_browser_arg(url: &Url) -> Option<String> {
20
+ let host = url.host_str()?;
21
+ let scheme = url.scheme();
22
+ let port = url.port().or_else(|| match scheme {
23
+ "http" => Some(80),
24
+ "socks5" => Some(1080),
25
+ _ => None,
26
+ })?;
27
+
28
+ match scheme {
29
+ "http" | "socks5" => Some(format!("--proxy-server={scheme}://{host}:{port}")),
30
+ _ => None,
31
+ }
32
+ }
33
+
34
+ pub struct MultiWindowState {
35
+ pub bghitapp_config: BghitappConfig,
36
+ pub tauri_config: Config,
37
+ next_window_index: AtomicU32,
38
+ }
39
+
40
+ impl MultiWindowState {
41
+ pub fn new(bghitapp_config: BghitappConfig, tauri_config: Config) -> Self {
42
+ Self {
43
+ bghitapp_config,
44
+ tauri_config,
45
+ next_window_index: AtomicU32::new(0),
46
+ }
47
+ }
48
+
49
+ fn next_window_label(&self) -> String {
50
+ let index = self.next_window_index.fetch_add(1, Ordering::Relaxed) + 1;
51
+ format!("bghitapp-{index}")
52
+ }
53
+ }
54
+
55
+ pub fn set_window(
56
+ app: &AppHandle,
57
+ config: &BghitappConfig,
58
+
59
+ tauri_config: &Config,
60
+ ) -> tauri::Result<WebviewWindow> {
61
+ build_window_with_label(app, config, tauri_config, "bghitapp")
62
+ }
63
+
64
+ pub fn build_splash_window(
65
+ app: &AppHandle,
66
+ _splash_asset: &str,
67
+ ) -> tauri::Result<WebviewWindow> {
68
+ let url = WebviewUrl::App(PathBuf::from("splash.html"));
69
+ let splash_width = 800.0;
70
+ let splash_height = 500.0;
71
+
72
+ // Calculate centered position on primary monitor
73
+ let (pos_x, pos_y) = if let Some(monitor) = app.primary_monitor().ok().flatten() {
74
+ let monitor_size = monitor.size();
75
+ let scale = monitor.scale_factor();
76
+ let mon_w = monitor_size.width as f64 / scale;
77
+ let mon_h = monitor_size.height as f64 / scale;
78
+ let monitor_pos = monitor.position();
79
+ let x = monitor_pos.x as f64 / scale + (mon_w - splash_width) / 2.0;
80
+ let y = monitor_pos.y as f64 / scale + (mon_h - splash_height) / 2.0;
81
+ (x, y)
82
+ } else {
83
+ // Fallback: rough center assuming 1920x1080
84
+ ((1920.0 - splash_width) / 2.0, (1080.0 - splash_height) / 2.0)
85
+ };
86
+
87
+ let window_builder = WebviewWindowBuilder::new(app, "splash", url)
88
+ .title("")
89
+ .visible(false)
90
+ .inner_size(splash_width, splash_height)
91
+ .position(pos_x, pos_y)
92
+ .resizable(false)
93
+ .decorations(false)
94
+ .skip_taskbar(true);
95
+
96
+ #[cfg(target_os = "macos")]
97
+ {
98
+ use tauri::TitleBarStyle;
99
+ window_builder = window_builder.title_bar_style(TitleBarStyle::Overlay);
100
+ }
101
+
102
+ window_builder.build()
103
+ }
104
+
105
+ pub fn open_additional_window(app: &AppHandle) -> tauri::Result<WebviewWindow> {
106
+ let state = app.state::<MultiWindowState>();
107
+ let label = state.next_window_label();
108
+ build_window_with_label(app, &state.bghitapp_config, &state.tauri_config, &label)
109
+ }
110
+
111
+ struct WindowBuildOptions<'a> {
112
+ label: &'a str,
113
+ url: WebviewUrl,
114
+ visible: bool,
115
+ new_window_features: Option<NewWindowFeatures>,
116
+ }
117
+
118
+ fn open_requested_window(
119
+ app: &AppHandle,
120
+ config: &BghitappConfig,
121
+ tauri_config: &Config,
122
+ target_url: Url,
123
+ features: NewWindowFeatures,
124
+ ) -> tauri::Result<WebviewWindow> {
125
+ let state = app.state::<MultiWindowState>();
126
+ let label = state.next_window_label();
127
+ let window = build_window(
128
+ app,
129
+ config,
130
+ tauri_config,
131
+ WindowBuildOptions {
132
+ label: &label,
133
+ url: WebviewUrl::External(target_url.clone()),
134
+ visible: true,
135
+ new_window_features: Some(features),
136
+ },
137
+ )?;
138
+
139
+ let title = target_url.host_str().unwrap_or(target_url.as_str());
140
+ let _ = window.set_title(title);
141
+ let _ = window.set_focus();
142
+
143
+ Ok(window)
144
+ }
145
+
146
+ pub fn open_additional_window_safe(app: &AppHandle) {
147
+ #[cfg(target_os = "windows")]
148
+ {
149
+ let app_handle = app.clone();
150
+ std::thread::spawn(move || {
151
+ if let Ok(window) = open_additional_window(&app_handle) {
152
+ let _ = window.show();
153
+ let _ = window.set_focus();
154
+ }
155
+ });
156
+ }
157
+
158
+ #[cfg(not(target_os = "windows"))]
159
+ {
160
+ if let Ok(window) = open_additional_window(app) {
161
+ let _ = window.show();
162
+ let _ = window.set_focus();
163
+ }
164
+ }
165
+ }
166
+
167
+ fn build_window_with_label(
168
+ app: &AppHandle,
169
+ config: &BghitappConfig,
170
+ tauri_config: &Config,
171
+ label: &str,
172
+ ) -> tauri::Result<WebviewWindow> {
173
+ let window_config = config.windows.first().ok_or_else(|| {
174
+ tauri::Error::Io(std::io::Error::new(
175
+ std::io::ErrorKind::InvalidData,
176
+ "bghitapp.json must define at least one window configuration",
177
+ ))
178
+ })?;
179
+ let url = match window_config.url_type.as_str() {
180
+ "web" => {
181
+ let parsed = window_config.url.parse().map_err(|err| {
182
+ tauri::Error::Io(std::io::Error::new(
183
+ std::io::ErrorKind::InvalidInput,
184
+ format!(
185
+ "Invalid 'web' url '{}' in bghitapp.json: {err}",
186
+ window_config.url
187
+ ),
188
+ ))
189
+ })?;
190
+ WebviewUrl::App(parsed)
191
+ }
192
+ "local" => WebviewUrl::App(PathBuf::from(&window_config.url)),
193
+ other => {
194
+ return Err(tauri::Error::Io(std::io::Error::new(
195
+ std::io::ErrorKind::InvalidInput,
196
+ format!("url_type must be 'web' or 'local', got '{other}'"),
197
+ )));
198
+ }
199
+ };
200
+
201
+ build_window(
202
+ app,
203
+ config,
204
+ tauri_config,
205
+ WindowBuildOptions {
206
+ label,
207
+ url,
208
+ visible: false,
209
+ new_window_features: None,
210
+ },
211
+ )
212
+ }
213
+
214
+ fn build_window(
215
+ app: &AppHandle,
216
+ config: &BghitappConfig,
217
+ tauri_config: &Config,
218
+ opts: WindowBuildOptions,
219
+ ) -> tauri::Result<WebviewWindow> {
220
+ let WindowBuildOptions {
221
+ label,
222
+ url,
223
+ visible,
224
+ new_window_features,
225
+ } = opts;
226
+ let package_name = tauri_config
227
+ .product_name
228
+ .clone()
229
+ .unwrap_or_else(|| "bghitapp".to_string());
230
+ let _data_dir = get_data_dir(app, package_name).map_err(tauri::Error::Io)?;
231
+
232
+ let window_config = config.windows.first().ok_or_else(|| {
233
+ tauri::Error::Io(std::io::Error::new(
234
+ std::io::ErrorKind::InvalidData,
235
+ "bghitapp.json must define at least one window configuration",
236
+ ))
237
+ })?;
238
+
239
+ let user_agent = config.user_agent.get();
240
+
241
+ let config_script = format!(
242
+ "window.bghitappConfig = {}",
243
+ serde_json::to_string(&window_config).unwrap_or_else(|_| "{}".to_string())
244
+ );
245
+
246
+ // Platform-specific title: macOS prefers empty, others fallback to product name
247
+ let effective_title = window_config.title.as_deref().unwrap_or_else(|| {
248
+ if cfg!(target_os = "macos") {
249
+ ""
250
+ } else {
251
+ tauri_config.product_name.as_deref().unwrap_or("")
252
+ }
253
+ });
254
+
255
+ let mut window_builder = WebviewWindowBuilder::new(app, label, url)
256
+ .title(effective_title)
257
+ .visible(visible)
258
+ .user_agent(user_agent)
259
+ .resizable(window_config.resizable)
260
+ .maximized(window_config.maximize);
261
+
262
+ #[cfg(target_os = "windows")]
263
+ {
264
+ let scale_factor = app
265
+ .primary_monitor()
266
+ .ok()
267
+ .flatten()
268
+ .map(|m| m.scale_factor())
269
+ .unwrap_or(1.0);
270
+ let logical_width = window_config.width / scale_factor;
271
+ let logical_height = window_config.height / scale_factor;
272
+ window_builder = window_builder.inner_size(logical_width, logical_height);
273
+ }
274
+
275
+ #[cfg(not(target_os = "windows"))]
276
+ {
277
+ window_builder = window_builder.inner_size(window_config.width, window_config.height);
278
+ }
279
+
280
+ window_builder = window_builder
281
+ .always_on_top(window_config.always_on_top)
282
+ .incognito(window_config.incognito);
283
+
284
+ #[cfg(any(target_os = "windows", target_os = "macos"))]
285
+ {
286
+ window_builder = window_builder.fullscreen(window_config.fullscreen);
287
+ }
288
+
289
+ if window_config.min_width > 0.0 || window_config.min_height > 0.0 {
290
+ let min_w = if window_config.min_width > 0.0 {
291
+ window_config.min_width
292
+ } else {
293
+ window_config.width
294
+ };
295
+ let min_h = if window_config.min_height > 0.0 {
296
+ window_config.min_height
297
+ } else {
298
+ window_config.height
299
+ };
300
+ window_builder = window_builder.min_inner_size(min_w, min_h);
301
+ }
302
+
303
+ if !window_config.enable_drag_drop {
304
+ window_builder = window_builder.disable_drag_drop_handler();
305
+ }
306
+
307
+ if window_config.new_window {
308
+ let app_handle = app.clone();
309
+ let popup_config = config.clone();
310
+ let popup_tauri_config = tauri_config.clone();
311
+ window_builder = window_builder.on_new_window(move |target_url, features| {
312
+ match open_requested_window(
313
+ &app_handle,
314
+ &popup_config,
315
+ &popup_tauri_config,
316
+ target_url,
317
+ features,
318
+ ) {
319
+ Ok(window) => NewWindowResponse::Create { window },
320
+ Err(error) => {
321
+ eprintln!("[BghitApp] Failed to open requested window: {error}");
322
+ NewWindowResponse::Deny
323
+ }
324
+ }
325
+ });
326
+ }
327
+
328
+ // Add initialization scripts. Order matters: bghitappConfig must land before
329
+ // any script that reads it (e.g. fullscreen polyfill checks for an opt-out
330
+ // flag), and toast must register `window.bghitappToast` before Rust code
331
+ // calls show_toast().
332
+ window_builder = window_builder
333
+ .initialization_script(&config_script)
334
+ .initialization_script(include_str!("../inject/find.js"))
335
+ .initialization_script(include_str!("../inject/toast.js"))
336
+ .initialization_script(include_str!("../inject/fullscreen.js"))
337
+ .initialization_script(include_str!("../inject/event.js"))
338
+ .initialization_script(include_str!("../inject/style.js"))
339
+ .initialization_script(include_str!("../inject/theme_refresh.js"))
340
+ .initialization_script(include_str!("../inject/auth.js"))
341
+ .initialization_script(include_str!("../inject/custom.js"));
342
+
343
+ // Conditionally inject offline detection script
344
+ if window_config.offline {
345
+ window_builder =
346
+ window_builder.initialization_script(include_str!("../inject/offline.js"));
347
+ }
348
+
349
+ // Inject splash transition script if splash is configured
350
+ if !window_config.splash.is_empty() {
351
+ window_builder =
352
+ window_builder.initialization_script(include_str!("../inject/splash-transition.js"));
353
+ }
354
+
355
+ #[cfg(target_os = "windows")]
356
+ let mut windows_browser_args = String::from("--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --disable-blink-features=AutomationControlled");
357
+
358
+ #[cfg(target_os = "linux")]
359
+ let mut linux_browser_args = String::from("--disable-blink-features=AutomationControlled");
360
+
361
+ if window_config.ignore_certificate_errors {
362
+ #[cfg(target_os = "windows")]
363
+ {
364
+ windows_browser_args.push_str(" --ignore-certificate-errors");
365
+ }
366
+
367
+ #[cfg(target_os = "linux")]
368
+ {
369
+ linux_browser_args.push_str(" --ignore-certificate-errors");
370
+ }
371
+
372
+ #[cfg(target_os = "macos")]
373
+ {
374
+ window_builder = window_builder.additional_browser_args("--ignore-certificate-errors");
375
+ }
376
+ }
377
+
378
+ if window_config.enable_wasm {
379
+ #[cfg(target_os = "windows")]
380
+ {
381
+ windows_browser_args.push_str(" --enable-features=SharedArrayBuffer");
382
+ windows_browser_args.push_str(" --enable-unsafe-webgpu");
383
+ }
384
+
385
+ #[cfg(target_os = "linux")]
386
+ {
387
+ linux_browser_args.push_str(" --enable-features=SharedArrayBuffer");
388
+ linux_browser_args.push_str(" --enable-unsafe-webgpu");
389
+ }
390
+
391
+ #[cfg(target_os = "macos")]
392
+ {
393
+ window_builder = window_builder
394
+ .additional_browser_args("--enable-features=SharedArrayBuffer")
395
+ .additional_browser_args("--enable-unsafe-webgpu");
396
+ }
397
+ }
398
+
399
+ let mut parsed_proxy_url: Option<Url> = None;
400
+
401
+ // Platform-specific configuration must be set before proxy on Windows/Linux
402
+ #[cfg(target_os = "macos")]
403
+ {
404
+ let title_bar_style = if window_config.hide_title_bar {
405
+ TitleBarStyle::Overlay
406
+ } else {
407
+ TitleBarStyle::Visible
408
+ };
409
+ window_builder = window_builder.title_bar_style(title_bar_style);
410
+
411
+ // Default to following system theme (None), only force dark when explicitly set
412
+ let theme = if window_config.dark_mode {
413
+ Some(Theme::Dark)
414
+ } else {
415
+ None // Follow system theme
416
+ };
417
+ window_builder = window_builder.theme(theme);
418
+ }
419
+
420
+ // Windows and Linux: set data_directory before proxy_url
421
+ #[cfg(not(target_os = "macos"))]
422
+ {
423
+ window_builder = window_builder.data_directory(_data_dir).theme(None);
424
+
425
+ if !config.proxy_url.is_empty() {
426
+ if let Ok(proxy_url) = Url::from_str(&config.proxy_url) {
427
+ parsed_proxy_url = Some(proxy_url.clone());
428
+ #[cfg(target_os = "windows")]
429
+ {
430
+ if let Some(arg) = build_proxy_browser_arg(&proxy_url) {
431
+ windows_browser_args.push(' ');
432
+ windows_browser_args.push_str(&arg);
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ #[cfg(target_os = "windows")]
439
+ {
440
+ window_builder = window_builder.additional_browser_args(&windows_browser_args);
441
+ }
442
+
443
+ #[cfg(target_os = "linux")]
444
+ {
445
+ window_builder = window_builder.additional_browser_args(&linux_browser_args);
446
+ }
447
+ }
448
+
449
+ // Set proxy after platform-specific configs (required for Windows/Linux)
450
+ if parsed_proxy_url.is_none() && !config.proxy_url.is_empty() {
451
+ if let Ok(proxy_url) = Url::from_str(&config.proxy_url) {
452
+ parsed_proxy_url = Some(proxy_url);
453
+ }
454
+ }
455
+
456
+ if let Some(proxy_url) = parsed_proxy_url {
457
+ window_builder = window_builder.proxy_url(proxy_url);
458
+ #[cfg(debug_assertions)]
459
+ println!("Proxy configured: {}", config.proxy_url);
460
+ }
461
+
462
+ if let Some(features) = new_window_features {
463
+ // Reuse only opener-provided position/size on macOS; sharing the opener
464
+ // WKWebViewConfiguration triggers duplicate WKScriptMessageHandler
465
+ // registrations on macOS 26+ and crashes the app (issue #1194).
466
+ #[cfg(target_os = "macos")]
467
+ {
468
+ if let Some(position) = features.position() {
469
+ window_builder = window_builder.position(position.x, position.y);
470
+ }
471
+
472
+ if let Some(size) = features.size() {
473
+ window_builder = window_builder.inner_size(size.width, size.height);
474
+ }
475
+
476
+ window_builder = window_builder.focused(true);
477
+ }
478
+
479
+ #[cfg(not(target_os = "macos"))]
480
+ {
481
+ window_builder = window_builder.window_features(features).focused(true);
482
+ }
483
+ }
484
+
485
+ // Capture webview-initiated downloads (blob:, data:, Content-Disposition,
486
+ // etc.) and write them to the OS Downloads folder. This is essential for
487
+ // sites with a strict Content-Security-Policy (e.g. Gemini): their
488
+ // `connect-src` blocks Tauri's IPC origin, so downloads cannot be routed
489
+ // through the JS bridge, and downloads triggered from a sandboxed iframe
490
+ // can't reach the IPC either. Letting the browser download natively and
491
+ // catching it here is independent of the page CSP and the IPC channel.
492
+ {
493
+ let download_handle = app.clone();
494
+ window_builder = window_builder.on_download(move |_webview, event| match event {
495
+ DownloadEvent::Requested { url, destination } => {
496
+ match download_handle.path().download_dir() {
497
+ Ok(download_dir) => {
498
+ let filename = destination
499
+ .file_name()
500
+ .map(|name| name.to_string_lossy().to_string())
501
+ .filter(|name| !name.is_empty())
502
+ .or_else(|| {
503
+ url.path_segments()
504
+ .and_then(|mut segments| segments.next_back())
505
+ .map(|segment| segment.to_string())
506
+ .filter(|segment| !segment.is_empty())
507
+ })
508
+ .unwrap_or_else(|| "download".to_string());
509
+
510
+ let target = download_dir.join(filename);
511
+ if let Some(path_str) = target.to_str() {
512
+ *destination = PathBuf::from(check_file_or_append(path_str));
513
+ }
514
+ }
515
+ Err(error) => {
516
+ eprintln!("[BghitApp] Failed to resolve download dir: {error}");
517
+ }
518
+ }
519
+ true
520
+ }
521
+ DownloadEvent::Finished {
522
+ url: _,
523
+ path: _,
524
+ success,
525
+ } => {
526
+ if let Some(window) = download_handle.get_webview_window("bghitapp") {
527
+ let message_type = if success {
528
+ MessageType::Success
529
+ } else {
530
+ MessageType::Failure
531
+ };
532
+ show_toast(&window, &get_download_message_with_lang(message_type, None));
533
+ }
534
+ true
535
+ }
536
+ _ => true,
537
+ });
538
+ }
539
+
540
+ window_builder = window_builder.on_navigation(|_| true);
541
+
542
+ window_builder.build()
543
+ }
544
+
545
+ #[cfg(all(test, target_os = "windows"))]
546
+ mod proxy_arg_tests {
547
+ use super::*;
548
+
549
+ fn parse(url: &str) -> Url {
550
+ Url::from_str(url).unwrap()
551
+ }
552
+
553
+ #[test]
554
+ fn http_url_with_explicit_port() {
555
+ let arg = build_proxy_browser_arg(&parse("http://127.0.0.1:7890")).unwrap();
556
+ assert_eq!(arg, "--proxy-server=http://127.0.0.1:7890");
557
+ }
558
+
559
+ #[test]
560
+ fn http_url_uses_default_port_when_missing() {
561
+ let arg = build_proxy_browser_arg(&parse("http://proxy.local")).unwrap();
562
+ assert_eq!(arg, "--proxy-server=http://proxy.local:80");
563
+ }
564
+
565
+ #[test]
566
+ fn socks5_url_uses_default_port_when_missing() {
567
+ let arg = build_proxy_browser_arg(&parse("socks5://proxy.local")).unwrap();
568
+ assert_eq!(arg, "--proxy-server=socks5://proxy.local:1080");
569
+ }
570
+
571
+ #[test]
572
+ fn https_scheme_is_not_supported_yet() {
573
+ // https proxies fall back to platform proxy_url; we only emit a CLI arg
574
+ // for http/socks5 today.
575
+ assert!(build_proxy_browser_arg(&parse("https://proxy.local:8443")).is_none());
576
+ }
577
+ }
@@ -0,0 +1,75 @@
1
+ // OAuth and Authentication Logic
2
+
3
+ // Check if URL matches OAuth/authentication patterns
4
+ function matchesAuthUrl(url, baseUrl = window.location.href) {
5
+ try {
6
+ const urlObj = new URL(url, baseUrl);
7
+ const hostname = urlObj.hostname.toLowerCase();
8
+ const pathname = urlObj.pathname.toLowerCase();
9
+ const fullUrl = urlObj.href.toLowerCase();
10
+
11
+ // Common OAuth providers and paths
12
+ const oauthPatterns = [
13
+ /accounts\.google\.com/,
14
+ /accounts\.google\.[a-z]+/,
15
+ /login\.microsoftonline\.com/,
16
+ /github\.com\/login/,
17
+ /facebook\.com\/.*\/dialog/,
18
+ /twitter\.com\/oauth/,
19
+ /appleid\.apple\.com/,
20
+ /\/oauth\//,
21
+ /\/auth\//,
22
+ /\/authorize/,
23
+ /\/login\/oauth/,
24
+ /\/signin/,
25
+ /\/login/,
26
+ /servicelogin/,
27
+ /\/o\/oauth2/,
28
+ ];
29
+
30
+ const isMatch = oauthPatterns.some(
31
+ (pattern) =>
32
+ pattern.test(hostname) ||
33
+ pattern.test(pathname) ||
34
+ pattern.test(fullUrl),
35
+ );
36
+
37
+ if (isMatch) {
38
+ console.log("[BghitApp] OAuth URL detected:", url);
39
+ }
40
+
41
+ return isMatch;
42
+ } catch (e) {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ // Check if URL is an OAuth/authentication link
48
+ function isAuthLink(url) {
49
+ return matchesAuthUrl(url);
50
+ }
51
+
52
+ // Check if this is an OAuth/authentication popup
53
+ function isAuthPopup(url, name) {
54
+ // Check for known authentication window names
55
+ const authWindowNames = [
56
+ "AppleAuthentication",
57
+ "oauth2",
58
+ "oauth",
59
+ "google-auth",
60
+ "auth-popup",
61
+ "signin",
62
+ "login",
63
+ ];
64
+
65
+ if (authWindowNames.includes(name)) {
66
+ return true;
67
+ }
68
+
69
+ return matchesAuthUrl(url);
70
+ }
71
+
72
+ // Export functions to global scope
73
+ window.matchesAuthUrl = matchesAuthUrl;
74
+ window.isAuthLink = isAuthLink;
75
+ window.isAuthPopup = isAuthPopup;
File without changes