pake-cli 3.11.10 โ 3.12.1
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.
- package/README.md +2 -2
- package/dist/cli.js +86 -28
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/app/invoke.rs +6 -35
- package/src-tauri/src/app/menu.rs +2 -4
- package/src-tauri/src/app/setup.rs +2 -3
- package/src-tauri/src/app/window.rs +12 -9
- package/src-tauri/src/inject/auth.js +20 -6
- package/src-tauri/src/inject/event.js +24 -5
- package/src-tauri/src/lib.rs +77 -4
- package/src-tauri/tauri.conf.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div align="center">
|
|
8
8
|
<a href="https://twitter.com/HiTw93" target="_blank">
|
|
9
9
|
<img alt="twitter" src="https://img.shields.io/badge/follow-Tw93-red?style=flat-square&logo=Twitter"></a>
|
|
10
|
-
<a href="https://t.me/+
|
|
10
|
+
<a href="https://t.me/+9f9gf4ZrFSQ2OWVl" target="_blank">
|
|
11
11
|
<img alt="telegram" src="https://img.shields.io/badge/chat-telegram-blueviolet?style=flat-square&logo=Telegram"></a>
|
|
12
12
|
<a href="https://github.com/tw93/Pake/releases" target="_blank">
|
|
13
13
|
<img alt="GitHub downloads" src="https://img.shields.io/github/downloads/tw93/Pake/total.svg?style=flat-square"></a>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
- ๐ **Lightweight**:
|
|
22
|
+
- ๐ **Lightweight**: Installer is nearly 20 times smaller than Electron packages, typically under 10M on disk
|
|
23
23
|
- ๐ **Fast**: Built with Rust Tauri, much faster than traditional JS frameworks with lower memory usage
|
|
24
24
|
- โก **Easy to use**: One-command packaging via CLI or online building, no complex configuration needed
|
|
25
25
|
- ๐ฆ **Feature-rich**: Supports shortcuts, immersive windows, drag & drop, style customization, ad removal
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { InvalidArgumentError, program as program$1, Option } from 'commander';
|
|
|
20
20
|
import fs$1 from 'fs';
|
|
21
21
|
|
|
22
22
|
var name = "pake-cli";
|
|
23
|
-
var version = "3.
|
|
23
|
+
var version = "3.12.1";
|
|
24
24
|
var description = "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ";
|
|
25
25
|
var engines = {
|
|
26
26
|
node: ">=18.0.0"
|
|
@@ -431,13 +431,14 @@ function needsTemporaryDebForZst(targets) {
|
|
|
431
431
|
*/
|
|
432
432
|
function buildWindowConfigOverrides(options, platform = asSupportedPlatform(process.platform)) {
|
|
433
433
|
const platformHideOnClose = options.hideOnClose ?? platform === 'darwin';
|
|
434
|
+
const platformHideTitleBar = platform === 'darwin' ? options.hideTitleBar : false;
|
|
434
435
|
return {
|
|
435
436
|
width: options.width,
|
|
436
437
|
height: options.height,
|
|
437
438
|
fullscreen: options.fullscreen,
|
|
438
439
|
maximize: options.maximize,
|
|
439
440
|
resizable: options.resizable ?? true,
|
|
440
|
-
hide_title_bar:
|
|
441
|
+
hide_title_bar: platformHideTitleBar,
|
|
441
442
|
activation_shortcut: options.activationShortcut,
|
|
442
443
|
always_on_top: options.alwaysOnTop,
|
|
443
444
|
dark_mode: options.darkMode,
|
|
@@ -712,6 +713,9 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
712
713
|
await copyTemplateConfigs();
|
|
713
714
|
const { appVersion, userAgent, showSystemTray, useLocalFile, identifier, name = 'pake-app', installerLanguage, wasm, camera, microphone, } = options;
|
|
714
715
|
const platform = asSupportedPlatform(process.platform);
|
|
716
|
+
if (options.hideTitleBar && platform !== 'darwin') {
|
|
717
|
+
logger.warn('โผ --hide-title-bar is only supported on macOS and will be ignored on this platform.');
|
|
718
|
+
}
|
|
715
719
|
const tauriConfWindowOptions = buildWindowConfigOverrides(options, platform);
|
|
716
720
|
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
|
717
721
|
tauriConf.productName = name;
|
|
@@ -1162,14 +1166,22 @@ class BaseBuilder {
|
|
|
1162
1166
|
return 0; // Disable proxy feature if version detection fails
|
|
1163
1167
|
}
|
|
1164
1168
|
}
|
|
1169
|
+
getCargoTargetDir() {
|
|
1170
|
+
return process.env.CARGO_TARGET_DIR || path.join('src-tauri', 'target');
|
|
1171
|
+
}
|
|
1172
|
+
resolveBuildPath(npmDirectory, buildPath) {
|
|
1173
|
+
return path.isAbsolute(buildPath)
|
|
1174
|
+
? buildPath
|
|
1175
|
+
: path.join(npmDirectory, buildPath);
|
|
1176
|
+
}
|
|
1165
1177
|
getBasePath() {
|
|
1166
1178
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
1167
|
-
return
|
|
1179
|
+
return path.join(this.getCargoTargetDir(), basePath, 'bundle');
|
|
1168
1180
|
}
|
|
1169
1181
|
getBuildAppPath(npmDirectory, fileName, fileType) {
|
|
1170
1182
|
// For app bundles on macOS, the directory is 'macos', not 'app'
|
|
1171
1183
|
const bundleDir = fileType.toLowerCase() === 'app' ? 'macos' : fileType.toLowerCase();
|
|
1172
|
-
return path.join(npmDirectory, this.getBasePath(), bundleDir, `${fileName}.${fileType}`);
|
|
1184
|
+
return path.join(this.resolveBuildPath(npmDirectory, this.getBasePath()), bundleDir, `${fileName}.${fileType}`);
|
|
1173
1185
|
}
|
|
1174
1186
|
/**
|
|
1175
1187
|
* Copy raw binary file to output directory
|
|
@@ -1196,9 +1208,9 @@ class BaseBuilder {
|
|
|
1196
1208
|
const binaryName = this.getBinaryName(appName);
|
|
1197
1209
|
// Handle cross-platform builds
|
|
1198
1210
|
if (this.options.multiArch || this.hasArchSpecificTarget()) {
|
|
1199
|
-
return path.join(npmDirectory, this.getArchSpecificPath(), basePath, binaryName);
|
|
1211
|
+
return path.join(this.resolveBuildPath(npmDirectory, this.getArchSpecificPath()), basePath, binaryName);
|
|
1200
1212
|
}
|
|
1201
|
-
return path.join(npmDirectory,
|
|
1213
|
+
return path.join(this.resolveBuildPath(npmDirectory, this.getCargoTargetDir()), basePath, binaryName);
|
|
1202
1214
|
}
|
|
1203
1215
|
/**
|
|
1204
1216
|
* Get the output path for the raw binary file
|
|
@@ -1229,7 +1241,7 @@ class BaseBuilder {
|
|
|
1229
1241
|
* Get architecture-specific path for binary
|
|
1230
1242
|
*/
|
|
1231
1243
|
getArchSpecificPath() {
|
|
1232
|
-
return
|
|
1244
|
+
return this.getCargoTargetDir(); // Override in subclasses if needed
|
|
1233
1245
|
}
|
|
1234
1246
|
}
|
|
1235
1247
|
BaseBuilder.ARCH_MAPPINGS = {
|
|
@@ -1260,7 +1272,9 @@ class MacBuilder extends BaseBuilder {
|
|
|
1260
1272
|
this.buildArch = validArchs.includes(options.targets || '')
|
|
1261
1273
|
? options.targets
|
|
1262
1274
|
: 'auto';
|
|
1263
|
-
|
|
1275
|
+
// `app` is a valid macOS bundle target (see merge.ts); honour it explicitly.
|
|
1276
|
+
if (options.targets === 'app' ||
|
|
1277
|
+
options.iterativeBuild ||
|
|
1264
1278
|
options.install ||
|
|
1265
1279
|
process.env.PAKE_CREATE_APP === '1') {
|
|
1266
1280
|
this.buildFormat = 'app';
|
|
@@ -1315,7 +1329,10 @@ class MacBuilder extends BaseBuilder {
|
|
|
1315
1329
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
1316
1330
|
const actualArch = this.getActualArch();
|
|
1317
1331
|
const target = this.getTauriTarget(actualArch, 'darwin');
|
|
1318
|
-
|
|
1332
|
+
if (!target) {
|
|
1333
|
+
throw new Error(`Unsupported architecture: ${actualArch} for macOS`);
|
|
1334
|
+
}
|
|
1335
|
+
return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
|
|
1319
1336
|
}
|
|
1320
1337
|
hasArchSpecificTarget() {
|
|
1321
1338
|
return true;
|
|
@@ -1323,7 +1340,10 @@ class MacBuilder extends BaseBuilder {
|
|
|
1323
1340
|
getArchSpecificPath() {
|
|
1324
1341
|
const actualArch = this.getActualArch();
|
|
1325
1342
|
const target = this.getTauriTarget(actualArch, 'darwin');
|
|
1326
|
-
|
|
1343
|
+
if (!target) {
|
|
1344
|
+
throw new Error(`Unsupported architecture: ${actualArch} for macOS`);
|
|
1345
|
+
}
|
|
1346
|
+
return path.join(this.getCargoTargetDir(), target);
|
|
1327
1347
|
}
|
|
1328
1348
|
}
|
|
1329
1349
|
|
|
@@ -1354,14 +1374,26 @@ class WinBuilder extends BaseBuilder {
|
|
|
1354
1374
|
getBasePath() {
|
|
1355
1375
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
1356
1376
|
const target = this.getTauriTarget(this.buildArch, 'win32');
|
|
1357
|
-
|
|
1377
|
+
if (!target) {
|
|
1378
|
+
throw new Error(`Unsupported architecture: ${this.buildArch} for Windows`);
|
|
1379
|
+
}
|
|
1380
|
+
return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
|
|
1358
1381
|
}
|
|
1359
1382
|
hasArchSpecificTarget() {
|
|
1360
1383
|
return true;
|
|
1361
1384
|
}
|
|
1362
1385
|
getArchSpecificPath() {
|
|
1363
1386
|
const target = this.getTauriTarget(this.buildArch, 'win32');
|
|
1364
|
-
|
|
1387
|
+
if (!target) {
|
|
1388
|
+
throw new Error(`Unsupported architecture: ${this.buildArch} for Windows`);
|
|
1389
|
+
}
|
|
1390
|
+
return path.join(this.getCargoTargetDir(), target);
|
|
1391
|
+
}
|
|
1392
|
+
getRawBinaryPath(appName) {
|
|
1393
|
+
return `${appName}.exe`;
|
|
1394
|
+
}
|
|
1395
|
+
getBinaryName(appName) {
|
|
1396
|
+
return `pake-${generateIdentifierSafeName(appName)}.exe`;
|
|
1365
1397
|
}
|
|
1366
1398
|
}
|
|
1367
1399
|
|
|
@@ -1556,7 +1588,10 @@ post_remove() {
|
|
|
1556
1588
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
1557
1589
|
if (this.buildArch === 'arm64') {
|
|
1558
1590
|
const target = this.getTauriTarget(this.buildArch, 'linux');
|
|
1559
|
-
|
|
1591
|
+
if (!target) {
|
|
1592
|
+
throw new Error(`Unsupported architecture: ${this.buildArch} for Linux`);
|
|
1593
|
+
}
|
|
1594
|
+
return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
|
|
1560
1595
|
}
|
|
1561
1596
|
return super.getBasePath();
|
|
1562
1597
|
}
|
|
@@ -1572,7 +1607,10 @@ post_remove() {
|
|
|
1572
1607
|
getArchSpecificPath() {
|
|
1573
1608
|
if (this.buildArch === 'arm64') {
|
|
1574
1609
|
const target = this.getTauriTarget(this.buildArch, 'linux');
|
|
1575
|
-
|
|
1610
|
+
if (!target) {
|
|
1611
|
+
throw new Error(`Unsupported architecture: ${this.buildArch} for Linux`);
|
|
1612
|
+
}
|
|
1613
|
+
return path.join(this.getCargoTargetDir(), target);
|
|
1576
1614
|
}
|
|
1577
1615
|
return super.getArchSpecificPath();
|
|
1578
1616
|
}
|
|
@@ -2410,6 +2448,20 @@ function normalizeUrl(urlToNormalize) {
|
|
|
2410
2448
|
throw new Error(`Your url "${urlWithProtocol}" is invalid: ${err.message}`);
|
|
2411
2449
|
}
|
|
2412
2450
|
}
|
|
2451
|
+
// Compiles a comma-separated domain list into a regex source for
|
|
2452
|
+
// internal_url_regex. Each domain is escaped and matched against the URL host
|
|
2453
|
+
// and its subdomains so path or query text cannot accidentally opt a link in.
|
|
2454
|
+
// Returns '' for empty input.
|
|
2455
|
+
function safeDomainsToRegex(domains) {
|
|
2456
|
+
const escaped = domains
|
|
2457
|
+
.split(',')
|
|
2458
|
+
.map((domain) => domain.trim().toLowerCase())
|
|
2459
|
+
.filter(Boolean)
|
|
2460
|
+
.map((domain) => domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
2461
|
+
return escaped.length
|
|
2462
|
+
? `^https?:\\/\\/(?:[^/?#@]+\\.)*(?:${escaped.join('|')})(?::\\d+)?(?:[/?#]|$)`
|
|
2463
|
+
: '';
|
|
2464
|
+
}
|
|
2413
2465
|
|
|
2414
2466
|
/**
|
|
2415
2467
|
* Error class used for user-facing CLI errors.
|
|
@@ -2490,6 +2542,10 @@ async function handleOptions(options, url) {
|
|
|
2490
2542
|
name: resolvedName,
|
|
2491
2543
|
identifier: resolveIdentifier(url, options.name, options.identifier),
|
|
2492
2544
|
};
|
|
2545
|
+
// --safe-domain is sugar over --internal-url-regex; an explicit regex wins.
|
|
2546
|
+
if (!options.internalUrlRegex && options.safeDomain) {
|
|
2547
|
+
appOptions.internalUrlRegex = safeDomainsToRegex(options.safeDomain);
|
|
2548
|
+
}
|
|
2493
2549
|
const iconPath = await handleIcon(appOptions, url);
|
|
2494
2550
|
appOptions.icon = iconPath || '';
|
|
2495
2551
|
return appOptions;
|
|
@@ -2538,6 +2594,7 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
2538
2594
|
startToTray: false,
|
|
2539
2595
|
forceInternalNavigation: false,
|
|
2540
2596
|
internalUrlRegex: '',
|
|
2597
|
+
safeDomain: '',
|
|
2541
2598
|
enableFind: false,
|
|
2542
2599
|
iterativeBuild: false,
|
|
2543
2600
|
zoom: 100,
|
|
@@ -2590,6 +2647,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2590
2647
|
return program$1
|
|
2591
2648
|
.addHelpText('beforeAll', logo)
|
|
2592
2649
|
.usage(`[url] [options]`)
|
|
2650
|
+
.helpOption('-h, --help', 'Show all CLI options')
|
|
2593
2651
|
.showHelpAfterError()
|
|
2594
2652
|
.argument('[url]', 'The web URL you want to package', validateUrlInput)
|
|
2595
2653
|
.option('--name <string>', 'Application name')
|
|
@@ -2629,7 +2687,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2629
2687
|
.addOption(new Option('--maximize', 'Start window maximized')
|
|
2630
2688
|
.default(DEFAULT_PAKE_OPTIONS.maximize)
|
|
2631
2689
|
.hideHelp())
|
|
2632
|
-
.addOption(new Option('--dark-mode', 'Force
|
|
2690
|
+
.addOption(new Option('--dark-mode', 'Force app to use dark mode (supports macOS, Windows, and Linux)')
|
|
2633
2691
|
.default(DEFAULT_PAKE_OPTIONS.darkMode)
|
|
2634
2692
|
.hideHelp())
|
|
2635
2693
|
.addOption(new Option('--disabled-web-shortcuts', 'Disabled webPage shortcuts')
|
|
@@ -2678,12 +2736,9 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2678
2736
|
.addOption(new Option('--start-to-tray', 'Start app minimized to tray')
|
|
2679
2737
|
.default(DEFAULT_PAKE_OPTIONS.startToTray)
|
|
2680
2738
|
.hideHelp())
|
|
2681
|
-
.addOption(new Option('--force-internal-navigation', 'Keep every link inside the Pake window instead of opening external handlers')
|
|
2682
|
-
.default(DEFAULT_PAKE_OPTIONS.
|
|
2683
|
-
.
|
|
2684
|
-
.addOption(new Option('--internal-url-regex <string>', 'Regex pattern to match URLs that should be considered internal')
|
|
2685
|
-
.default(DEFAULT_PAKE_OPTIONS.internalUrlRegex)
|
|
2686
|
-
.hideHelp())
|
|
2739
|
+
.addOption(new Option('--force-internal-navigation', 'Keep every link inside the Pake window instead of opening external handlers').default(DEFAULT_PAKE_OPTIONS.forceInternalNavigation))
|
|
2740
|
+
.addOption(new Option('--internal-url-regex <string>', 'Regex pattern to match URLs that should be considered internal').default(DEFAULT_PAKE_OPTIONS.internalUrlRegex))
|
|
2741
|
+
.addOption(new Option('--safe-domain <domains>', 'Comma-separated domains kept inside the app (e.g. SSO/workspace callbacks)').default(DEFAULT_PAKE_OPTIONS.safeDomain))
|
|
2687
2742
|
.addOption(new Option('--enable-find', 'Enable in-page Find UI with Cmd/Ctrl+F/G shortcuts')
|
|
2688
2743
|
.default(DEFAULT_PAKE_OPTIONS.enableFind)
|
|
2689
2744
|
.hideHelp())
|
|
@@ -2714,9 +2769,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2714
2769
|
.addOption(new Option('--iterative-build', 'Turn on rapid build mode (app only, no dmg/deb/msi), good for debugging')
|
|
2715
2770
|
.default(DEFAULT_PAKE_OPTIONS.iterativeBuild)
|
|
2716
2771
|
.hideHelp())
|
|
2717
|
-
.addOption(new Option('--new-window', 'Allow sites to open new windows (for auth flows, tabs, branches)')
|
|
2718
|
-
.default(DEFAULT_PAKE_OPTIONS.newWindow)
|
|
2719
|
-
.hideHelp())
|
|
2772
|
+
.addOption(new Option('--new-window', 'Allow sites to open new windows (for auth flows, tabs, branches)').default(DEFAULT_PAKE_OPTIONS.newWindow))
|
|
2720
2773
|
.addOption(new Option('--install', 'Auto-install app to /Applications (macOS) after build and remove local bundle')
|
|
2721
2774
|
.default(DEFAULT_PAKE_OPTIONS.install)
|
|
2722
2775
|
.hideHelp())
|
|
@@ -2729,14 +2782,19 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2729
2782
|
.version(packageJson.version, '-v, --version')
|
|
2730
2783
|
.configureHelp({
|
|
2731
2784
|
sortSubcommands: true,
|
|
2785
|
+
visibleOptions: (command) => {
|
|
2786
|
+
const options = [...command.options];
|
|
2787
|
+
const helpOption = command
|
|
2788
|
+
._helpOption;
|
|
2789
|
+
if (helpOption) {
|
|
2790
|
+
options.push(helpOption);
|
|
2791
|
+
}
|
|
2792
|
+
return options;
|
|
2793
|
+
},
|
|
2732
2794
|
optionTerm: (option) => {
|
|
2733
|
-
if (option.flags === '-v, --version' || option.flags === '-h, --help')
|
|
2734
|
-
return '';
|
|
2735
2795
|
return option.flags;
|
|
2736
2796
|
},
|
|
2737
2797
|
optionDescription: (option) => {
|
|
2738
|
-
if (option.flags === '-v, --version' || option.flags === '-h, --help')
|
|
2739
|
-
return '';
|
|
2740
2798
|
return option.description;
|
|
2741
2799
|
},
|
|
2742
2800
|
});
|
package/package.json
CHANGED
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
|
@@ -7,7 +7,6 @@ use tauri::http::Method;
|
|
|
7
7
|
use tauri::{command, AppHandle, Manager, Url, WebviewWindow};
|
|
8
8
|
use tauri_plugin_http::reqwest::{ClientBuilder, Request};
|
|
9
9
|
|
|
10
|
-
#[cfg(target_os = "macos")]
|
|
11
10
|
use tauri::Theme;
|
|
12
11
|
|
|
13
12
|
static BADGE_COUNT: AtomicI64 = AtomicI64::new(0);
|
|
@@ -183,40 +182,12 @@ pub fn set_dock_badge_label(app: AppHandle, label: Option<String>) -> Result<(),
|
|
|
183
182
|
|
|
184
183
|
#[command]
|
|
185
184
|
pub async fn update_theme_mode(app: AppHandle, mode: String) {
|
|
186
|
-
#[cfg(target_os = "macos")]
|
|
187
|
-
{
|
|
188
|
-
if let Some(window) = app.get_webview_window("pake") {
|
|
189
|
-
let theme = if mode == "dark" {
|
|
190
|
-
Theme::Dark
|
|
191
|
-
} else {
|
|
192
|
-
Theme::Light
|
|
193
|
-
};
|
|
194
|
-
let _ = window.set_theme(Some(theme));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
#[cfg(not(target_os = "macos"))]
|
|
198
|
-
{
|
|
199
|
-
let _ = app;
|
|
200
|
-
let _ = mode;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
#[command]
|
|
205
|
-
#[allow(unreachable_code)]
|
|
206
|
-
pub fn clear_cache_and_restart(app: AppHandle) -> Result<(), String> {
|
|
207
185
|
if let Some(window) = app.get_webview_window("pake") {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
Err(e) => {
|
|
215
|
-
eprintln!("Failed to clear browsing data: {}", e);
|
|
216
|
-
Err(format!("Failed to clear browsing data: {}", e))
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
Err("Main window not found".to_string())
|
|
186
|
+
let theme = if mode == "dark" {
|
|
187
|
+
Theme::Dark
|
|
188
|
+
} else {
|
|
189
|
+
Theme::Light
|
|
190
|
+
};
|
|
191
|
+
let _ = window.set_theme(Some(theme));
|
|
221
192
|
}
|
|
222
193
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Menu functionality is only used on macOS
|
|
2
|
-
#![cfg(target_os = "macos")]
|
|
3
|
-
|
|
1
|
+
// Menu functionality is only used on macOS; the module is gated in app/mod.rs.
|
|
4
2
|
use crate::app::window::open_additional_window_safe;
|
|
5
3
|
use tauri::menu::{AboutMetadata, Menu, MenuItem, PredefinedMenuItem, Submenu};
|
|
6
4
|
use tauri::{AppHandle, Manager, Wry};
|
|
@@ -308,7 +306,7 @@ pub fn handle_menu_click(app_handle: &AppHandle, id: &str) {
|
|
|
308
306
|
}
|
|
309
307
|
"clear_cache_restart" => {
|
|
310
308
|
if let Some(window) = app_handle.get_webview_window("pake") {
|
|
311
|
-
if
|
|
309
|
+
if window.clear_all_browsing_data().is_ok() {
|
|
312
310
|
app_handle.restart();
|
|
313
311
|
}
|
|
314
312
|
}
|
|
@@ -66,8 +66,8 @@ pub fn set_system_tray(
|
|
|
66
66
|
}
|
|
67
67
|
_ => (),
|
|
68
68
|
})
|
|
69
|
-
.on_tray_icon_event(move |tray, event|
|
|
70
|
-
TrayIconEvent::Click { button, .. }
|
|
69
|
+
.on_tray_icon_event(move |tray, event| {
|
|
70
|
+
if let TrayIconEvent::Click { button, .. } = event {
|
|
71
71
|
if button == tauri::tray::MouseButton::Left {
|
|
72
72
|
if let Some(window) = tray.app_handle().get_webview_window("pake") {
|
|
73
73
|
let is_visible = window.is_visible().unwrap_or(false);
|
|
@@ -84,7 +84,6 @@ pub fn set_system_tray(
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
_ => {}
|
|
88
87
|
});
|
|
89
88
|
|
|
90
89
|
let resolved_icon = if tray_icon_path.is_empty() {
|
|
@@ -12,8 +12,10 @@ use tauri::{
|
|
|
12
12
|
AppHandle, Config, Manager, Url, WebviewUrl, WebviewWindow, WebviewWindowBuilder,
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
use tauri::Theme;
|
|
16
|
+
|
|
15
17
|
#[cfg(target_os = "macos")]
|
|
16
|
-
use tauri::
|
|
18
|
+
use tauri::TitleBarStyle;
|
|
17
19
|
|
|
18
20
|
#[cfg(target_os = "windows")]
|
|
19
21
|
fn build_proxy_browser_arg(url: &Url) -> Option<String> {
|
|
@@ -344,6 +346,14 @@ fn build_window(
|
|
|
344
346
|
|
|
345
347
|
let mut parsed_proxy_url: Option<Url> = None;
|
|
346
348
|
|
|
349
|
+
// Default to following the system theme (None), only force dark when explicitly set.
|
|
350
|
+
// Computed once; the matching platform block below is the sole consumer.
|
|
351
|
+
let theme = if window_config.dark_mode {
|
|
352
|
+
Some(Theme::Dark)
|
|
353
|
+
} else {
|
|
354
|
+
None // Follow system theme
|
|
355
|
+
};
|
|
356
|
+
|
|
347
357
|
// Platform-specific configuration must be set before proxy on Windows/Linux
|
|
348
358
|
#[cfg(target_os = "macos")]
|
|
349
359
|
{
|
|
@@ -353,20 +363,13 @@ fn build_window(
|
|
|
353
363
|
TitleBarStyle::Visible
|
|
354
364
|
};
|
|
355
365
|
window_builder = window_builder.title_bar_style(title_bar_style);
|
|
356
|
-
|
|
357
|
-
// Default to following system theme (None), only force dark when explicitly set
|
|
358
|
-
let theme = if window_config.dark_mode {
|
|
359
|
-
Some(Theme::Dark)
|
|
360
|
-
} else {
|
|
361
|
-
None // Follow system theme
|
|
362
|
-
};
|
|
363
366
|
window_builder = window_builder.theme(theme);
|
|
364
367
|
}
|
|
365
368
|
|
|
366
369
|
// Windows and Linux: set data_directory before proxy_url
|
|
367
370
|
#[cfg(not(target_os = "macos"))]
|
|
368
371
|
{
|
|
369
|
-
window_builder = window_builder.data_directory(_data_dir).theme(
|
|
372
|
+
window_builder = window_builder.data_directory(_data_dir).theme(theme);
|
|
370
373
|
|
|
371
374
|
if !config.proxy_url.is_empty() {
|
|
372
375
|
if let Ok(proxy_url) = Url::from_str(&config.proxy_url) {
|
|
@@ -27,12 +27,26 @@ function matchesAuthUrl(url, baseUrl = window.location.href) {
|
|
|
27
27
|
/\/o\/oauth2/,
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
// Enterprise SSO. Match identity providers on the host, and SAML/SSO/ADFS on
|
|
31
|
+
// the pathname with endpoint-shaped patterns only, so ordinary pages such as
|
|
32
|
+
// /settings/sso/providers (or a query string carrying an SSO URL) are not
|
|
33
|
+
// misread as authentication.
|
|
34
|
+
const enterpriseHostPatterns = [/(^|\.)okta\.com$/, /(^|\.)onelogin\.com$/];
|
|
35
|
+
const enterprisePathPatterns = [
|
|
36
|
+
/\/saml2?\/(sso|acs|login|metadata|consume|redirect|callback|continue)/,
|
|
37
|
+
/\/sso\/(saml|oidc|oauth|login|authorize|redirect|callback|acs|start|continue|metadata)/,
|
|
38
|
+
/\/adfs\/ls\b/,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const isMatch =
|
|
42
|
+
oauthPatterns.some(
|
|
43
|
+
(pattern) =>
|
|
44
|
+
pattern.test(hostname) ||
|
|
45
|
+
pattern.test(pathname) ||
|
|
46
|
+
pattern.test(fullUrl),
|
|
47
|
+
) ||
|
|
48
|
+
enterpriseHostPatterns.some((pattern) => pattern.test(hostname)) ||
|
|
49
|
+
enterprisePathPatterns.some((pattern) => pattern.test(pathname));
|
|
36
50
|
|
|
37
51
|
if (isMatch) {
|
|
38
52
|
console.log("[Pake] OAuth URL detected:", url);
|
|
@@ -459,20 +459,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
459
459
|
const absoluteUrl = hrefUrl.href;
|
|
460
460
|
let filename = anchorElement.download || getFilenameFromUrl(absoluteUrl);
|
|
461
461
|
|
|
462
|
-
// Keep OAuth/authentication flows inside the app
|
|
462
|
+
// Keep OAuth/authentication flows inside the app. Without --new-window,
|
|
463
|
+
// navigate in place so the SSO redirect chain and callback stay in the
|
|
464
|
+
// webview instead of falling through to the system browser.
|
|
463
465
|
if (window.isAuthLink(absoluteUrl)) {
|
|
464
466
|
console.log("[Pake] Handling OAuth navigation in-app:", absoluteUrl);
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
e.stopImmediatePropagation();
|
|
465
469
|
|
|
466
470
|
if (window.pakeConfig?.new_window) {
|
|
467
|
-
e.preventDefault();
|
|
468
|
-
e.stopImmediatePropagation();
|
|
469
|
-
|
|
470
471
|
openAuthNavigation(
|
|
471
472
|
originalWindowOpen,
|
|
472
473
|
absoluteUrl,
|
|
473
474
|
"_blank",
|
|
474
475
|
"width=1200,height=800,scrollbars=yes,resizable=yes",
|
|
475
476
|
);
|
|
477
|
+
} else {
|
|
478
|
+
window.location.href = absoluteUrl;
|
|
476
479
|
}
|
|
477
480
|
|
|
478
481
|
return;
|
|
@@ -488,7 +491,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
488
491
|
}
|
|
489
492
|
|
|
490
493
|
if (isInternalUrl(absoluteUrl)) {
|
|
491
|
-
//
|
|
494
|
+
// With --new-window the Rust on_new_window handler opens an in-app
|
|
495
|
+
// window; without it, deferring to the native handler sends the
|
|
496
|
+
// _blank target to the system browser and strands SSO callbacks.
|
|
497
|
+
// Navigate in place so internal links stay inside the webview.
|
|
498
|
+
if (!window.pakeConfig?.new_window) {
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
e.stopImmediatePropagation();
|
|
501
|
+
window.location.href = absoluteUrl;
|
|
502
|
+
}
|
|
492
503
|
return;
|
|
493
504
|
}
|
|
494
505
|
|
|
@@ -592,6 +603,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
592
603
|
return null;
|
|
593
604
|
}
|
|
594
605
|
|
|
606
|
+
// With --new-window the native handler opens an in-app window; without it,
|
|
607
|
+
// originalWindowOpen would route the internal target to the system browser
|
|
608
|
+
// and strand SSO callbacks, so navigate in place instead.
|
|
609
|
+
if (!window.pakeConfig?.new_window) {
|
|
610
|
+
window.location.href = absoluteUrl;
|
|
611
|
+
return window;
|
|
612
|
+
}
|
|
613
|
+
|
|
595
614
|
return originalWindowOpen.call(window, absoluteUrl, name, specs);
|
|
596
615
|
} catch (error) {
|
|
597
616
|
return originalWindowOpen.call(window, url, name, specs);
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -16,11 +16,13 @@ const PAKE_LINUX_WEBKIT_SAFE_MODE: &str = "PAKE_LINUX_WEBKIT_SAFE_MODE";
|
|
|
16
16
|
const WEBKIT_DISABLE_DMABUF_RENDERER: &str = "WEBKIT_DISABLE_DMABUF_RENDERER";
|
|
17
17
|
#[cfg(target_os = "linux")]
|
|
18
18
|
const WEBKIT_DISABLE_COMPOSITING_MODE: &str = "WEBKIT_DISABLE_COMPOSITING_MODE";
|
|
19
|
+
#[cfg(target_os = "linux")]
|
|
20
|
+
const GDK_BACKEND: &str = "GDK_BACKEND";
|
|
19
21
|
|
|
20
22
|
use app::{
|
|
21
23
|
invoke::{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
clear_dock_badge, download_file, increment_dock_badge, send_notification, set_dock_badge,
|
|
25
|
+
set_dock_badge_label, update_theme_mode,
|
|
24
26
|
},
|
|
25
27
|
setup::{set_global_shortcut, set_system_tray},
|
|
26
28
|
window::{open_additional_window_safe, set_window, MultiWindowState},
|
|
@@ -66,6 +68,34 @@ fn should_enable_linux_webkit_safe_mode_from_values(
|
|
|
66
68
|
!is_niri_session
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
#[cfg(any(target_os = "linux", test))]
|
|
72
|
+
fn should_force_wayland_gdk_backend(
|
|
73
|
+
gdk_backend: Option<&str>,
|
|
74
|
+
wayland_display: Option<&str>,
|
|
75
|
+
display: Option<&str>,
|
|
76
|
+
) -> bool {
|
|
77
|
+
// Respect an explicit user choice.
|
|
78
|
+
if is_non_empty_env_value(gdk_backend) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// On pure Wayland compositors without XWayland (e.g. Niri), $DISPLAY is unset
|
|
83
|
+
// and GTK defaults to the X11 backend, which aborts with "Failed to initialize
|
|
84
|
+
// GTK". Wayland is then the only viable backend, so forcing it is safe.
|
|
85
|
+
is_non_empty_env_value(wayland_display) && !is_non_empty_env_value(display)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[cfg(target_os = "linux")]
|
|
89
|
+
fn apply_linux_gdk_backend() {
|
|
90
|
+
if should_force_wayland_gdk_backend(
|
|
91
|
+
std::env::var(GDK_BACKEND).ok().as_deref(),
|
|
92
|
+
std::env::var("WAYLAND_DISPLAY").ok().as_deref(),
|
|
93
|
+
std::env::var("DISPLAY").ok().as_deref(),
|
|
94
|
+
) {
|
|
95
|
+
std::env::set_var(GDK_BACKEND, "wayland");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
69
99
|
#[cfg(target_os = "linux")]
|
|
70
100
|
fn apply_linux_webkit_runtime_flags() {
|
|
71
101
|
let safe_mode = std::env::var(PAKE_LINUX_WEBKIT_SAFE_MODE).ok();
|
|
@@ -103,7 +133,10 @@ fn apply_linux_webkit_runtime_flags() {
|
|
|
103
133
|
|
|
104
134
|
pub fn run_app() {
|
|
105
135
|
#[cfg(target_os = "linux")]
|
|
106
|
-
|
|
136
|
+
{
|
|
137
|
+
apply_linux_gdk_backend();
|
|
138
|
+
apply_linux_webkit_runtime_flags();
|
|
139
|
+
}
|
|
107
140
|
|
|
108
141
|
let (pake_config, tauri_config) = get_pake_config();
|
|
109
142
|
let tauri_app = tauri::Builder::default();
|
|
@@ -159,7 +192,6 @@ pub fn run_app() {
|
|
|
159
192
|
set_dock_badge_label,
|
|
160
193
|
clear_dock_badge,
|
|
161
194
|
update_theme_mode,
|
|
162
|
-
clear_cache_and_restart,
|
|
163
195
|
])
|
|
164
196
|
.setup(move |app| {
|
|
165
197
|
app.manage(MultiWindowState::new(
|
|
@@ -329,4 +361,45 @@ mod tests {
|
|
|
329
361
|
);
|
|
330
362
|
}
|
|
331
363
|
}
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn forces_wayland_backend_on_pure_wayland() {
|
|
367
|
+
assert!(should_force_wayland_gdk_backend(
|
|
368
|
+
None,
|
|
369
|
+
Some("wayland-0"),
|
|
370
|
+
None
|
|
371
|
+
));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#[test]
|
|
375
|
+
fn forces_wayland_backend_when_display_is_blank() {
|
|
376
|
+
assert!(should_force_wayland_gdk_backend(
|
|
377
|
+
None,
|
|
378
|
+
Some("wayland-0"),
|
|
379
|
+
Some(" ")
|
|
380
|
+
));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#[test]
|
|
384
|
+
fn keeps_default_backend_when_x11_display_present() {
|
|
385
|
+
assert!(!should_force_wayland_gdk_backend(
|
|
386
|
+
None,
|
|
387
|
+
Some("wayland-0"),
|
|
388
|
+
Some(":0")
|
|
389
|
+
));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#[test]
|
|
393
|
+
fn keeps_default_backend_without_wayland_display() {
|
|
394
|
+
assert!(!should_force_wayland_gdk_backend(None, None, None));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[test]
|
|
398
|
+
fn respects_explicit_gdk_backend_override() {
|
|
399
|
+
assert!(!should_force_wayland_gdk_backend(
|
|
400
|
+
Some("x11"),
|
|
401
|
+
Some("wayland-0"),
|
|
402
|
+
None
|
|
403
|
+
));
|
|
404
|
+
}
|
|
332
405
|
}
|