pake-cli 3.5.3 → 3.6.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.
@@ -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("[Pake] 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;
@@ -394,6 +394,12 @@ document.addEventListener("DOMContentLoaded", () => {
394
394
  const absoluteUrl = hrefUrl.href;
395
395
  let filename = anchorElement.download || getFilenameFromUrl(absoluteUrl);
396
396
 
397
+ // Early check: Allow OAuth/authentication links to navigate naturally
398
+ if (window.isAuthLink(absoluteUrl)) {
399
+ console.log("[Pake] Allowing OAuth navigation to:", absoluteUrl);
400
+ return;
401
+ }
402
+
397
403
  // Handle _blank links: same domain navigates in-app, cross-domain opens new window
398
404
  if (target === "_blank") {
399
405
  if (forceInternalNavigation) {
@@ -477,7 +483,8 @@ document.addEventListener("DOMContentLoaded", () => {
477
483
  // Rewrite the window.open function.
478
484
  const originalWindowOpen = window.open;
479
485
  window.open = function (url, name, specs) {
480
- if (name === "AppleAuthentication") {
486
+ // Allow authentication popups to open normally
487
+ if (window.isAuthPopup(url, name)) {
481
488
  return originalWindowOpen.call(window, url, name, specs);
482
489
  }
483
490
 
@@ -0,0 +1,124 @@
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ // Helper: Calculate brightness from RGB color
3
+ const isDarkColor = (color) => {
4
+ if (!color) return false;
5
+ const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
6
+ if (!match) return false;
7
+ const r = parseInt(match[1]);
8
+ const g = parseInt(match[2]);
9
+ const b = parseInt(match[3]);
10
+ // Standard luminance formula
11
+ const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
12
+ return luminance < 128;
13
+ };
14
+
15
+ // Debounce helper
16
+ const debounce = (func, wait) => {
17
+ let timeout;
18
+ return function executedFunction(...args) {
19
+ const later = () => {
20
+ clearTimeout(timeout);
21
+ func(...args);
22
+ };
23
+ clearTimeout(timeout);
24
+ timeout = setTimeout(later, wait);
25
+ };
26
+ };
27
+
28
+ // Function to detect and send theme to Rust
29
+ const updateTheme = () => {
30
+ let mode = "light";
31
+ let detected = false;
32
+
33
+ // Strategy 1: Explicit DOM Class/Attribute (High Priority)
34
+ // Many apps use specific classes for hard-coded themes
35
+ const doc = document.documentElement;
36
+ const body = document.body;
37
+
38
+ const isExplicitDark =
39
+ doc.classList.contains("dark") ||
40
+ body.classList.contains("dark") ||
41
+ doc.getAttribute("data-theme") === "dark" ||
42
+ body.getAttribute("data-theme") === "dark" ||
43
+ doc.style.colorScheme === "dark";
44
+
45
+ const isExplicitLight =
46
+ doc.classList.contains("light") ||
47
+ body.classList.contains("light") ||
48
+ doc.getAttribute("data-theme") === "light" ||
49
+ body.getAttribute("data-theme") === "light" ||
50
+ doc.style.colorScheme === "light";
51
+
52
+ if (isExplicitDark) {
53
+ mode = "dark";
54
+ detected = true;
55
+ } else if (isExplicitLight) {
56
+ mode = "light";
57
+ detected = true;
58
+ }
59
+
60
+ // Strategy 2: Computed Background Color (Fallback & Verification)
61
+ // If no explicit class is found, or to double-check, look at the actual background color.
62
+ // This is useful when the site relies purely on CSS media queries without classes.
63
+ if (!detected) {
64
+ const bodyBg = window.getComputedStyle(document.body).backgroundColor;
65
+ const htmlBg = window.getComputedStyle(
66
+ document.documentElement,
67
+ ).backgroundColor;
68
+
69
+ // Check body first, then html
70
+ if (bodyBg && bodyBg !== "rgba(0, 0, 0, 0)" && bodyBg !== "transparent") {
71
+ mode = isDarkColor(bodyBg) ? "dark" : "light";
72
+ } else if (
73
+ htmlBg &&
74
+ htmlBg !== "rgba(0, 0, 0, 0)" &&
75
+ htmlBg !== "transparent"
76
+ ) {
77
+ mode = isDarkColor(htmlBg) ? "dark" : "light";
78
+ } else {
79
+ // Strategy 3: System Preference (Last Resort)
80
+ if (
81
+ window.matchMedia &&
82
+ window.matchMedia("(prefers-color-scheme: dark)").matches
83
+ ) {
84
+ mode = "dark";
85
+ }
86
+ }
87
+ }
88
+
89
+ // Send to Rust
90
+ if (window.__TAURI__ && window.__TAURI__.core) {
91
+ window.__TAURI__.core.invoke("update_theme_mode", { mode });
92
+ }
93
+ };
94
+
95
+ // Debounced version of updateTheme
96
+ const debouncedUpdateTheme = debounce(updateTheme, 200);
97
+
98
+ // Initial check
99
+ // Delay slightly to ensure styles are applied
100
+ setTimeout(updateTheme, 100);
101
+
102
+ // Watch for system theme changes
103
+ window
104
+ .matchMedia("(prefers-color-scheme: dark)")
105
+ .addEventListener("change", updateTheme);
106
+
107
+ // Watch for DOM changes
108
+ // We observe attributes for class changes, and also style changes just in case
109
+ const observer = new MutationObserver((mutations) => {
110
+ debouncedUpdateTheme();
111
+ });
112
+
113
+ observer.observe(document.documentElement, {
114
+ attributes: true,
115
+ attributeFilter: ["class", "data-theme", "style"],
116
+ subtree: false,
117
+ });
118
+
119
+ observer.observe(document.body, {
120
+ attributes: true,
121
+ attributeFilter: ["class", "data-theme", "style"],
122
+ subtree: false,
123
+ });
124
+ });
@@ -9,8 +9,14 @@ use tauri_plugin_window_state::StateFlags;
9
9
  #[cfg(target_os = "macos")]
10
10
  use std::time::Duration;
11
11
 
12
+ use tauri::menu::{AboutMetadata, Menu, MenuItem, PredefinedMenuItem, Submenu};
13
+ use tauri_plugin_opener::OpenerExt; // Add this
14
+
12
15
  use app::{
13
- invoke::{download_file, download_file_by_binary, send_notification},
16
+ invoke::{
17
+ clear_cache_and_restart, download_file, download_file_by_binary, send_notification,
18
+ update_theme_mode,
19
+ },
14
20
  setup::{set_global_shortcut, set_system_tray},
15
21
  window::set_window,
16
22
  };
@@ -42,7 +48,8 @@ pub fn run_app() {
42
48
  .plugin(tauri_plugin_oauth::init())
43
49
  .plugin(tauri_plugin_http::init())
44
50
  .plugin(tauri_plugin_shell::init())
45
- .plugin(tauri_plugin_notification::init());
51
+ .plugin(tauri_plugin_notification::init())
52
+ .plugin(tauri_plugin_opener::init()); // Add this
46
53
 
47
54
  // Only add single instance plugin if multiple instances are not allowed
48
55
  if !multi_instance {
@@ -60,8 +67,246 @@ pub fn run_app() {
60
67
  download_file,
61
68
  download_file_by_binary,
62
69
  send_notification,
70
+ update_theme_mode,
71
+ clear_cache_and_restart,
63
72
  ])
64
73
  .setup(move |app| {
74
+ // --- Menu Construction Start ---
75
+ let pake_version = env!("CARGO_PKG_VERSION");
76
+ let pake_menu_item_title = format!("Built with Pake V{}", pake_version);
77
+
78
+ // App Menu (macOS specific, e.g., "Pake")
79
+ let app_menu = Submenu::new(app, "Pake", true)?;
80
+ let about_metadata = AboutMetadata::default();
81
+ app_menu.append(&PredefinedMenuItem::about(
82
+ app,
83
+ Some("Pake"),
84
+ Some(about_metadata),
85
+ )?)?;
86
+ app_menu.append(&PredefinedMenuItem::separator(app)?)?;
87
+ app_menu.append(&PredefinedMenuItem::services(app, None)?)?;
88
+ app_menu.append(&PredefinedMenuItem::separator(app)?)?;
89
+ app_menu.append(&PredefinedMenuItem::hide(app, None)?)?;
90
+ app_menu.append(&PredefinedMenuItem::hide_others(app, None)?)?;
91
+ app_menu.append(&PredefinedMenuItem::show_all(app, None)?)?;
92
+ app_menu.append(&PredefinedMenuItem::separator(app)?)?;
93
+ app_menu.append(&PredefinedMenuItem::quit(app, None)?)?;
94
+
95
+ // File Menu
96
+ let file_menu = Submenu::new(app, "File", true)?;
97
+ file_menu.append(&PredefinedMenuItem::close_window(app, None)?)?;
98
+ file_menu.append(&PredefinedMenuItem::separator(app)?)?;
99
+ file_menu.append(&MenuItem::with_id(
100
+ app,
101
+ "clear_cache_restart",
102
+ "Clear Cache & Restart",
103
+ true,
104
+ Some("CmdOrCtrl+Shift+Backspace"),
105
+ )?)?;
106
+
107
+ // Edit Menu
108
+ let edit_menu = Submenu::new(app, "Edit", true)?;
109
+ edit_menu.append(&PredefinedMenuItem::undo(app, None)?)?;
110
+ edit_menu.append(&PredefinedMenuItem::redo(app, None)?)?;
111
+ edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
112
+ edit_menu.append(&PredefinedMenuItem::cut(app, None)?)?;
113
+ edit_menu.append(&PredefinedMenuItem::copy(app, None)?)?;
114
+ edit_menu.append(&PredefinedMenuItem::paste(app, None)?)?;
115
+ edit_menu.append(&PredefinedMenuItem::select_all(app, None)?)?;
116
+ edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
117
+ edit_menu.append(&MenuItem::with_id(
118
+ app,
119
+ "copy_url",
120
+ "Copy URL",
121
+ true,
122
+ Some("CmdOrCtrl+L"),
123
+ )?)?;
124
+
125
+ // View Menu
126
+ let view_menu = Submenu::new(app, "View", true)?;
127
+ view_menu.append(&MenuItem::with_id(
128
+ app,
129
+ "reload",
130
+ "Reload",
131
+ true,
132
+ Some("CmdOrCtrl+R"),
133
+ )?)?;
134
+ view_menu.append(&PredefinedMenuItem::separator(app)?)?;
135
+ view_menu.append(&MenuItem::with_id(
136
+ app,
137
+ "zoom_in",
138
+ "Zoom In",
139
+ true,
140
+ Some("CmdOrCtrl+="),
141
+ )?)?;
142
+ view_menu.append(&MenuItem::with_id(
143
+ app,
144
+ "zoom_out",
145
+ "Zoom Out",
146
+ true,
147
+ Some("CmdOrCtrl+-"),
148
+ )?)?;
149
+ view_menu.append(&MenuItem::with_id(
150
+ app,
151
+ "zoom_reset",
152
+ "Actual Size",
153
+ true,
154
+ Some("CmdOrCtrl+0"),
155
+ )?)?;
156
+ view_menu.append(&PredefinedMenuItem::separator(app)?)?;
157
+ view_menu.append(&PredefinedMenuItem::fullscreen(app, None)?)?;
158
+ view_menu.append(&PredefinedMenuItem::separator(app)?)?;
159
+ view_menu.append(&MenuItem::with_id(
160
+ app,
161
+ "toggle_devtools",
162
+ "Toggle Developer Tools",
163
+ cfg!(debug_assertions),
164
+ Some("CmdOrCtrl+Option+I"),
165
+ )?)?;
166
+
167
+ // Navigation Menu
168
+ let navigation_menu = Submenu::new(app, "Navigation", true)?;
169
+ navigation_menu.append(&MenuItem::with_id(
170
+ app,
171
+ "go_back",
172
+ "Back",
173
+ true,
174
+ Some("CmdOrCtrl+["),
175
+ )?)?;
176
+ navigation_menu.append(&MenuItem::with_id(
177
+ app,
178
+ "go_forward",
179
+ "Forward",
180
+ true,
181
+ Some("CmdOrCtrl+]"),
182
+ )?)?;
183
+ navigation_menu.append(&MenuItem::with_id(
184
+ app,
185
+ "go_home",
186
+ "Go Home",
187
+ true,
188
+ Some("CmdOrCtrl+Shift+H"),
189
+ )?)?;
190
+
191
+ // Window Menu
192
+ let window_menu = Submenu::new(app, "Window", true)?;
193
+ window_menu.append(&PredefinedMenuItem::minimize(app, None)?)?;
194
+ window_menu.append(&PredefinedMenuItem::maximize(app, None)?)?;
195
+ window_menu.append(&PredefinedMenuItem::separator(app)?)?;
196
+ window_menu.append(&MenuItem::with_id(
197
+ app,
198
+ "always_on_top",
199
+ "Toggle Always on Top",
200
+ true,
201
+ None::<&str>,
202
+ )?)?;
203
+ window_menu.append(&PredefinedMenuItem::separator(app)?)?;
204
+ window_menu.append(&PredefinedMenuItem::close_window(app, None)?)?;
205
+
206
+ // Help Menu (Custom)
207
+ let help_menu = Submenu::new(app, "Help", true)?;
208
+ let github_item = MenuItem::with_id(
209
+ app,
210
+ "pake_github_link",
211
+ &pake_menu_item_title,
212
+ true,
213
+ None::<&str>,
214
+ )?;
215
+ help_menu.append(&github_item)?;
216
+
217
+ // Construct the Menu Bar
218
+ let menu = Menu::with_items(
219
+ app,
220
+ &[
221
+ &app_menu,
222
+ &file_menu,
223
+ &edit_menu,
224
+ &view_menu,
225
+ &navigation_menu,
226
+ &window_menu,
227
+ &help_menu,
228
+ ],
229
+ )?;
230
+
231
+ app.set_menu(menu)?;
232
+
233
+ // Event Handling for Custom Menu Item
234
+ app.on_menu_event(move |app_handle, event| {
235
+ match event.id().as_ref() {
236
+ "pake_github_link" => {
237
+ let _ = app_handle
238
+ .opener()
239
+ .open_url("https://github.com/tw93/Pake", None::<&str>);
240
+ }
241
+ "reload" => {
242
+ if let Some(window) = app_handle.get_webview_window("pake") {
243
+ let _ = window.eval("window.location.reload()");
244
+ }
245
+ }
246
+ "toggle_devtools" => {
247
+ #[cfg(debug_assertions)] // Only allow in debug builds
248
+ if let Some(window) = app_handle.get_webview_window("pake") {
249
+ if window.is_devtools_open() {
250
+ window.close_devtools();
251
+ } else {
252
+ window.open_devtools();
253
+ }
254
+ }
255
+ }
256
+ "zoom_in" => {
257
+ if let Some(window) = app_handle.get_webview_window("pake") {
258
+ let _ = window.eval("zoomIn()");
259
+ }
260
+ }
261
+ "zoom_out" => {
262
+ if let Some(window) = app_handle.get_webview_window("pake") {
263
+ let _ = window.eval("zoomOut()");
264
+ }
265
+ }
266
+ "zoom_reset" => {
267
+ if let Some(window) = app_handle.get_webview_window("pake") {
268
+ let _ = window.eval("setZoom('100%')");
269
+ }
270
+ }
271
+ "go_back" => {
272
+ if let Some(window) = app_handle.get_webview_window("pake") {
273
+ let _ = window.eval("window.history.back()");
274
+ }
275
+ }
276
+ "go_forward" => {
277
+ if let Some(window) = app_handle.get_webview_window("pake") {
278
+ let _ = window.eval("window.history.forward()");
279
+ }
280
+ }
281
+ "go_home" => {
282
+ if let Some(window) = app_handle.get_webview_window("pake") {
283
+ let _ = window.eval("window.location.href = window.pakeConfig.url");
284
+ }
285
+ }
286
+ "copy_url" => {
287
+ if let Some(window) = app_handle.get_webview_window("pake") {
288
+ let _ =
289
+ window.eval("navigator.clipboard.writeText(window.location.href)");
290
+ }
291
+ }
292
+ "clear_cache_restart" => {
293
+ if let Some(window) = app_handle.get_webview_window("pake") {
294
+ if let Ok(_) = window.clear_all_browsing_data() {
295
+ app_handle.restart();
296
+ }
297
+ }
298
+ }
299
+ "always_on_top" => {
300
+ if let Some(window) = app_handle.get_webview_window("pake") {
301
+ let is_on_top = window.is_always_on_top().unwrap_or(false);
302
+ let _ = window.set_always_on_top(!is_on_top);
303
+ }
304
+ }
305
+ _ => {}
306
+ }
307
+ });
308
+ // --- Menu Construction End ---
309
+
65
310
  let window = set_window(app, &pake_config, &tauri_config);
66
311
  set_system_tray(
67
312
  app.app_handle(),
@@ -10,7 +10,8 @@
10
10
  "id": "pake-tray"
11
11
  },
12
12
  "security": {
13
- "headers": {}
13
+ "headers": {},
14
+ "csp": null
14
15
  }
15
16
  },
16
17
  "build": {