Skip to content

Commit 62d53cf

Browse files
feat(launcher): add new CLI flags, clear user data, and unsaved changes guard (#99)
* refactor: rename BotBrowserConsole to BotBrowserLauncher * feat: add kernel management, proxy management, and setup scripts * fix: enable macOS native Edit menu for Cmd+C/V/X/A keyboard shortcuts * chore: upgrade Neutralino runtime to 6.4.0 * chore: remove Neutralino binaries from repo, download via neu update * fix: improve macOS clipboard support with JS fallback for Cmd+V paste * fix: expand macOS clipboard fallback to Cmd+C/X, auto-download Neutralino via postinstall * chore: upgrade Neutralino to 6.5.0 and simplify setup scripts * fix: fix PowerShell setup script ZIP download corruption * feat: enhance proxy workflow with IP check, quick change, and reusable input component * fix: correct checkbox color override and setup script stdin handling * feat: improve profile deletion with auto-stop, group WebRTC settings, prioritize custom exec path * feat: add port protection toggle, fix kernel auto-update cleanup and UI refresh * fix: detect kernel assets by file extension to support renamed Windows/Linux packages * fix: handle non-browser process exits, add crash detection, validate kernel executable paths * docs: add --bot-time-seed documentation, changelog 2026-03-03, value range and per-context cross-links * feat: add proxy save/check, improve table layout and kernel date display - Add "Save to proxy list" button in proxy input for reusing manually entered proxies - Add batch proxy connectivity check with IP/status display in proxy management - Sort proxy list by creation time (newest first) - Fix kernel date display: parse from asset filename, fix timezone offset bug - Simplify profile status column to icon-only (play/stop) with tooltips - Remove year from "Last Launch" date to prevent truncation - Widen window to 1080px, use fixed table layout to prevent horizontal scrollbar - Add column width constraints for profile and proxy tables * feat: redesign profile editor with left nav, advanced config modes, and new fields - Add left-side anchor navigation with scrollspy (IntersectionObserver) - Split Fingerprint into separate Noise and Rendering sections - Replace expansion panel with flat Advanced section layout - Add mode toggles for Executable (kernel/custom), Cookies and Bookmarks (file/input) - Add FPS dropdown (profile/real/custom number) instead of free-text input - Add Save IP button in proxy section for explicit IP saving - Add new fields: Time Seed, Proxy Bypass Regex, Cookies, Bookmarks, Custom Headers - Put username/password on same row in proxy input - Widen status column to prevent icon clipping * fix: replace Save IP with Save to proxy list, unify dropdown option labels * fix: include username and password in proxy duplicate detection Normalize both sides with `|| ''` to handle undefined values, and add password to the comparison so proxies with the same host:port but different credentials are treated as distinct entries. * fix: prevent editing running profiles and remove auto-save IP on proxy check - Block profile editing when browser is running/launching/stopping, show alert dialog instead of silently darkening the background - Remove onIpCheckResult that auto-saved checked IP to proxyIp field * feat(launcher): add automatic self-update with GitHub commit tracking Check for launcher updates on startup and every hour by comparing the local commit hash against the latest launcher-specific commit from GitHub API (path=launcher). When an update is found, silently download, rebuild, and prompt the user to restart. - Add UpdateService with periodic check, ZIP download, and rebuild - Show update status (checking/downloading/building/ready) in sidebar - Display current version (commit hash) at sidebar bottom - Update setup scripts to save commit hash after install/build - Fix sidebar footer layout to stay at bottom via flex container * feat(launcher): improve table UX, add new CLI flags, and proxy input enhancements - Separate row highlight (single click) from checkbox selection (batch ops) - Add double-click to edit on both profile and proxy tables - Add clear proxy button in profile editor - Combine proxy type/host/port into single row layout - Add --bot-stack-seed and --bot-network-info-override CLI flags - Update noise seed from float to integer range - Stack seed uses dropdown (profile/real/custom) like FPS - Rearrange noise settings: seed+scale+timeseed row, fps+stackseed row - Widen proxy type column in proxy list table * fix(launcher): auto-highlight new profiles/proxies and default noise text rects off - Return created profile/proxy ID from dialog so the table highlights the new row after creation, matching the edit-then-highlight behavior - Change botConfigNoiseTextRects default from on to off * feat(launcher): add new CLI flags, clear user data, and unsaved changes guard - Add --bot-config-orientation dropdown in Display & Input section with all orientation variants, noted as mobile profiles only - Add --bot-gpu-emulation toggle in Behavior section for auto GPU rendering backend selection - Add "Clear User Data" action in both profile edit dialog and profile list context menu to delete user-data-dir - Guard profile and proxy edit dialogs against accidental close: Escape, backdrop click, and Cancel now prompt when forms have unsaved changes
1 parent aad48f8 commit 62d53cf

File tree

8 files changed

+170
-5
lines changed

8 files changed

+170
-5
lines changed

launcher/src/app/app.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,10 @@
255255
<mat-icon>local_fire_department</mat-icon>
256256
<span>Warmup</span>
257257
</button>
258+
<button mat-menu-item (click)="clearUserData(element)">
259+
<mat-icon>delete_sweep</mat-icon>
260+
<span>Clear User Data</span>
261+
</button>
258262
}
259263
</mat-menu>
260264
</div>

launcher/src/app/app.component.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,31 @@ export class AppComponent implements AfterViewInit {
287287
}
288288
}
289289

290+
clearUserData(browserProfile: BrowserProfile): void {
291+
this.#dialog
292+
.open(ConfirmDialogComponent, {
293+
data: {
294+
message: `Clear user data for "${browserProfile.basicInfo.profileName}"? This deletes cookies, cache, local storage, and all browsing data. The profile settings will be kept.`,
295+
},
296+
})
297+
.afterClosed()
298+
.subscribe(async (result: boolean) => {
299+
if (!result) return;
300+
301+
try {
302+
const userDataDirPath = await this.#browserProfileService.getBrowserProfileUserDataDirPath(browserProfile);
303+
await Neutralino.filesystem.remove(userDataDirPath);
304+
} catch (error) {
305+
// Directory may not exist yet, which is fine
306+
console.log('Clear user data:', error);
307+
}
308+
309+
this.#dialog.open(AlertDialogComponent, {
310+
data: { message: 'User data cleared successfully.' },
311+
});
312+
});
313+
}
314+
290315
async refreshProfiles(): Promise<void> {
291316
this.loading = true;
292317
try {

launcher/src/app/data/browser-profile.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export const MediaTypesOptions: MediaTypesOption[] = ['expand', 'profile', 'real
6767
export type ColorScheme = 'light' | 'dark';
6868
export const ColorSchemes: ColorScheme[] = ['light', 'dark'];
6969

70+
// Orientation options
71+
export type OrientationOption = 'profile' | 'landscape' | 'portrait' | 'landscape-primary' | 'landscape-secondary' | 'portrait-primary' | 'portrait-secondary';
72+
export const OrientationOptions: OrientationOption[] = ['profile', 'landscape', 'portrait', 'landscape-primary', 'landscape-secondary', 'portrait-primary', 'portrait-secondary'];
73+
7074
// Behavior toggles
7175
export interface BehaviorToggles {
7276
botLocalDns?: boolean;
@@ -77,6 +81,7 @@ export interface BehaviorToggles {
7781
botDisableConsoleMessage?: boolean;
7882
botPortProtection?: boolean;
7983
botNetworkInfoOverride?: boolean;
84+
botGpuEmulation?: boolean;
8085
}
8186

8287
// Identity & Locale config
@@ -107,6 +112,7 @@ export interface DisplayInputConfig {
107112
botConfigScreen?: ProfileReal;
108113
botConfigKeyboard?: ProfileReal;
109114
botConfigFonts?: FontOption;
115+
botConfigOrientation?: OrientationOption;
110116
botConfigColorScheme?: ColorScheme;
111117
botConfigDisableDeviceScaleFactor?: boolean;
112118
}

launcher/src/app/edit-browser-profile.component.html

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ <h2 mat-dialog-title>{{ isEdit ? 'Edit' : 'Create' }} Browser Profile</h2>
286286
}
287287
</mat-select>
288288
</mat-form-field>
289+
290+
<mat-form-field class="flex-1">
291+
<mat-label>Orientation</mat-label>
292+
<mat-select formControlName="botConfigOrientation">
293+
<mat-option value="">Default</mat-option>
294+
@for (opt of orientationOptions; track opt) {
295+
<mat-option [value]="opt">{{ opt }}</mat-option>
296+
}
297+
</mat-select>
298+
<mat-hint>Mobile profiles only (Android/iPhone/iPad)</mat-hint>
299+
</mat-form-field>
289300
</div>
290301

291302
<mat-slide-toggle formControlName="botConfigDisableDeviceScaleFactor" class="standalone-toggle">
@@ -499,6 +510,11 @@ <h2 mat-dialog-title>{{ isEdit ? 'Edit' : 'Create' }} Browser Profile</h2>
499510
Network Info Override
500511
<span class="toggle-hint">Use profile-defined navigator.connection values (default: off)</span>
501512
</mat-slide-toggle>
513+
514+
<mat-slide-toggle formControlName="botGpuEmulation">
515+
GPU Emulation
516+
<span class="toggle-hint">Auto-select optimal GPU rendering backend (default: off)</span>
517+
</mat-slide-toggle>
502518
</form>
503519
</div>
504520

@@ -600,7 +616,12 @@ <h2 mat-dialog-title>{{ isEdit ? 'Edit' : 'Create' }} Browser Profile</h2>
600616
</div>
601617
</mat-dialog-content>
602618
<mat-dialog-actions align="end">
603-
<button mat-button mat-dialog-close>Cancel</button>
619+
@if (isEdit) {
620+
<button mat-button color="warn" (click)="clearUserData()">
621+
Clear User Data
622+
</button>
623+
}
624+
<button mat-button (click)="onCancel()">Cancel</button>
604625
<button mat-button cdkFocusInitial (click)="onConfirmClick()">
605626
{{ isEdit ? 'Save' : 'Create' }}
606627
</button>

launcher/src/app/edit-browser-profile.component.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ColorSchemes,
2424
FontOptions,
2525
MediaTypesOptions,
26+
OrientationOptions,
2627
Platforms,
2728
ProfileRealDisabledOptions,
2829
ProfileRealOptions,
@@ -109,6 +110,7 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
109110
readonly fontOptions = FontOptions;
110111
readonly mediaTypesOptions = MediaTypesOptions;
111112
readonly colorSchemes = ColorSchemes;
113+
readonly orientationOptions = OrientationOptions;
112114

113115
readonly basicInfoFormGroup = this.#formBuilder.group<BasicInfo>({
114116
profileName: this.#injectedData?.basicInfo.profileName || 'New Profile',
@@ -155,6 +157,7 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
155157
botDisableConsoleMessage: this.#injectedData?.launchOptions?.behavior?.botDisableConsoleMessage ?? true,
156158
botPortProtection: this.#injectedData?.launchOptions?.behavior?.botPortProtection,
157159
botNetworkInfoOverride: this.#injectedData?.launchOptions?.behavior?.botNetworkInfoOverride,
160+
botGpuEmulation: this.#injectedData?.launchOptions?.behavior?.botGpuEmulation,
158161
});
159162

160163
// Identity & Locale - default: browserBrand=chrome
@@ -185,6 +188,7 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
185188
botConfigScreen: this.#injectedData?.launchOptions?.displayInput?.botConfigScreen ?? 'real',
186189
botConfigKeyboard: this.#injectedData?.launchOptions?.displayInput?.botConfigKeyboard ?? 'profile',
187190
botConfigFonts: this.#injectedData?.launchOptions?.displayInput?.botConfigFonts ?? 'profile',
191+
botConfigOrientation: this.#injectedData?.launchOptions?.displayInput?.botConfigOrientation,
188192
botConfigColorScheme: this.#injectedData?.launchOptions?.displayInput?.botConfigColorScheme ?? 'light',
189193
botConfigDisableDeviceScaleFactor:
190194
this.#injectedData?.launchOptions?.displayInput?.botConfigDisableDeviceScaleFactor,
@@ -299,6 +303,13 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
299303
async ngOnInit() {
300304
// Load proxies
301305
this.proxies = await this.#proxyService.getAllProxies();
306+
307+
// Unsaved changes guard
308+
this.#dialogRef.disableClose = true;
309+
this.#dialogRef.backdropClick().subscribe(() => this.#confirmClose());
310+
this.#dialogRef.keydownEvents().subscribe((event) => {
311+
if (event.key === 'Escape') this.#confirmClose();
312+
});
302313
}
303314

304315
ngAfterViewInit() {
@@ -533,6 +544,39 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
533544
});
534545
}
535546

547+
onCancel(): void {
548+
this.#confirmClose();
549+
}
550+
551+
#isFormDirty(): boolean {
552+
return (
553+
this.basicInfoFormGroup.dirty ||
554+
this.behaviorGroup.dirty ||
555+
this.identityLocaleGroup.dirty ||
556+
this.customUserAgentGroup.dirty ||
557+
this.displayInputGroup.dirty ||
558+
this.noiseGroup.dirty ||
559+
this.renderingMediaGroup.dirty ||
560+
this.proxyConfigGroup.dirty ||
561+
this.advancedConfigGroup.dirty
562+
);
563+
}
564+
565+
#confirmClose(): void {
566+
if (!this.#isFormDirty()) {
567+
this.#dialogRef.close();
568+
return;
569+
}
570+
this.#dialog
571+
.open(ConfirmDialogComponent, {
572+
data: { message: 'You have unsaved changes. Discard and close?' },
573+
})
574+
.afterClosed()
575+
.subscribe((result: boolean) => {
576+
if (result) this.#dialogRef.close();
577+
});
578+
}
579+
536580
#handleFileSelection(filePath: string): void {
537581
Neutralino.filesystem
538582
.readFile(filePath)
@@ -583,6 +627,30 @@ export class EditBrowserProfileComponent implements OnInit, AfterViewInit, OnDes
583627
return true;
584628
}
585629

630+
clearUserData(): void {
631+
if (!this.#injectedData) return;
632+
const profile = this.#injectedData;
633+
this.#dialog
634+
.open(ConfirmDialogComponent, {
635+
data: {
636+
message: `Clear user data for "${profile.basicInfo.profileName}"? This deletes cookies, cache, local storage, and all browsing data. The profile settings will be kept.`,
637+
},
638+
})
639+
.afterClosed()
640+
.subscribe(async (result: boolean) => {
641+
if (!result) return;
642+
try {
643+
const userDataDirPath = await this.#browserProfileService.getBrowserProfileUserDataDirPath(profile);
644+
await Neutralino.filesystem.remove(userDataDirPath);
645+
} catch (error) {
646+
console.log('Clear user data:', error);
647+
}
648+
this.#dialog.open(AlertDialogComponent, {
649+
data: { message: 'User data cleared successfully.' },
650+
});
651+
});
652+
}
653+
586654
async onConfirmClick(): Promise<void> {
587655
console.log('onConfirmClick called');
588656

launcher/src/app/proxy-management/edit-proxy.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ <h2 mat-dialog-title>{{ isEdit ? 'Edit' : 'New' }} Proxy</h2>
1717
</mat-dialog-content>
1818

1919
<mat-dialog-actions align="end">
20-
<button mat-button mat-dialog-close>Cancel</button>
20+
<button mat-button (click)="onCancel()">Cancel</button>
2121
<button mat-flat-button color="primary" (click)="onConfirmClick()">Confirm</button>
2222
</mat-dialog-actions>

launcher/src/app/proxy-management/edit-proxy.component.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { CommonModule } from '@angular/common';
2-
import { Component, inject } from '@angular/core';
2+
import { Component, inject, type OnInit } from '@angular/core';
33
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
44
import { MatButtonModule } from '@angular/material/button';
5-
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
5+
import { MAT_DIALOG_DATA, MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
66
import { MatFormFieldModule } from '@angular/material/form-field';
77
import { MatInputModule } from '@angular/material/input';
88
import { v4 as uuidv4 } from 'uuid';
99
import type { Proxy, ProxyType } from '../data/proxy';
1010
import type { ParsedProxy } from '../shared/proxy-parser.service';
11+
import { ConfirmDialogComponent } from '../shared/confirm-dialog.component';
1112
import { ProxyInputComponent } from '../shared/proxy-input.component';
1213
import { ProxyService } from '../shared/proxy.service';
1314

@@ -26,8 +27,9 @@ import { ProxyService } from '../shared/proxy.service';
2627
templateUrl: './edit-proxy.component.html',
2728
styleUrl: './edit-proxy.component.scss',
2829
})
29-
export class EditProxyComponent {
30+
export class EditProxyComponent implements OnInit {
3031
readonly #proxyService = inject(ProxyService);
32+
readonly #dialog = inject(MatDialog);
3133
readonly #dialogRef = inject(MatDialogRef<EditProxyComponent>);
3234
readonly #injectedData = inject<Proxy | undefined>(MAT_DIALOG_DATA);
3335
readonly #formBuilder = inject(FormBuilder);
@@ -50,6 +52,14 @@ export class EditProxyComponent {
5052

5153
#currentProxyValue: ParsedProxy | null = this.proxyValue;
5254

55+
ngOnInit(): void {
56+
this.#dialogRef.disableClose = true;
57+
this.#dialogRef.backdropClick().subscribe(() => this.#confirmClose());
58+
this.#dialogRef.keydownEvents().subscribe((event) => {
59+
if (event.key === 'Escape') this.#confirmClose();
60+
});
61+
}
62+
5363
onProxyValueChange(value: ParsedProxy | null): void {
5464
this.#currentProxyValue = value;
5565

@@ -59,6 +69,34 @@ export class EditProxyComponent {
5969
}
6070
}
6171

72+
onCancel(): void {
73+
this.#confirmClose();
74+
}
75+
76+
#confirmClose(): void {
77+
if (!this.nameFormGroup.dirty && !this.#hasProxyChanged()) {
78+
this.#dialogRef.close();
79+
return;
80+
}
81+
this.#dialog
82+
.open(ConfirmDialogComponent, {
83+
data: { message: 'You have unsaved changes. Discard and close?' },
84+
})
85+
.afterClosed()
86+
.subscribe((result: boolean) => {
87+
if (result) this.#dialogRef.close();
88+
});
89+
}
90+
91+
#hasProxyChanged(): boolean {
92+
const orig = this.proxyValue;
93+
const curr = this.#currentProxyValue;
94+
if (!orig && !curr) return false;
95+
if (!orig || !curr) return true;
96+
return orig.type !== curr.type || orig.host !== curr.host || orig.port !== curr.port
97+
|| orig.username !== curr.username || orig.password !== curr.password;
98+
}
99+
62100
async onConfirmClick(): Promise<void> {
63101
const proxyData = this.#currentProxyValue;
64102
if (!proxyData?.host || !proxyData?.port) return;

launcher/src/app/shared/browser-launcher.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ export class BrowserLauncherService {
279279
if (opts?.behavior?.botDisableConsoleMessage) args.push('--bot-disable-console-message');
280280
if (opts?.behavior?.botPortProtection) args.push('--bot-port-protection');
281281
if (opts?.behavior?.botNetworkInfoOverride) args.push('--bot-network-info-override');
282+
if (opts?.behavior?.botGpuEmulation) args.push('--bot-gpu-emulation');
282283

283284
// Identity & Locale
284285
if (opts?.identityLocale?.botConfigBrowserBrand)
@@ -320,6 +321,8 @@ export class BrowserLauncherService {
320321
args.push(`--bot-config-keyboard=${opts.displayInput.botConfigKeyboard}`);
321322
if (opts?.displayInput?.botConfigFonts)
322323
args.push(`--bot-config-fonts=${opts.displayInput.botConfigFonts}`);
324+
if (opts?.displayInput?.botConfigOrientation)
325+
args.push(`--bot-config-orientation=${opts.displayInput.botConfigOrientation}`);
323326
if (opts?.displayInput?.botConfigColorScheme)
324327
args.push(`--bot-config-color-scheme=${opts.displayInput.botConfigColorScheme}`);
325328
if (opts?.displayInput?.botConfigDisableDeviceScaleFactor)

0 commit comments

Comments
 (0)