fix: HK login, UI improvements, and native WebView2 cookie seeding (supersedes #229)#230
Conversation
…tale cookie and misc improvements - Replace vuedraggable with direct SortableJS for account list drag reorder - Add NSIS installer target with embedded WebView2 bootstrapper - Fix stale bfWebToken cookie in auth_aspx by reading live value from cookie jar - Add updateSessionService to keep frontend session in sync on game switch - Restore VideoReport button with updated beanfun-event URL - Add account limit notice display and disable add-account button at limit - Add QR code image copy to clipboard on right-click - Disable global context menu in production - Config store: silently handle read-only file write failures - Update README with installer vs portable version table - Update tests to match SortableJS migration and config store changes
Port the two missing WPF buttons (bfb_Gash_Click + btn_Deposite_Click) that were omitted during the Tauri rewrite. The i18n keys already existed; this wires up the backend command and frontend UI.
The region picker always showed on every launch even when loginRegion and loginMethod were already persisted in Config.xml. This implements the WPF loginMethodInit parity: watch config.loaded and router.replace to the correct form (id-pass or QR) based on saved preferences. Back buttons on login forms now navigate with ?pick=1 so the user can still reach the region picker without triggering the auto-redirect.
Disable native decorations and enable transparency so the glass panel itself becomes the visible window. Lock the window size and grant the permissions the custom TitleBar needs to drag, minimize, close, and resize the frame. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
The custom TitleBar replaces the native window chrome that we disabled in tauri.conf. It exposes drag-to-move on the title area, minimize/close buttons, and a slot for per-page actions (region switch, settings, about). The displayed title and icon come from route.meta so each page can self-describe. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Wire the global pieces that the glass-panel-as-window architecture needs across the whole app: - Make :root, html, body and #app transparent so only the page's glass card is visible. - Disable text selection globally (with an opt-in carve-out for inputs / textareas / contenteditable) so the app feels native rather than like a web page. - Thin rounded scrollbars matching the design system. - Element Plus MessageBox glass override so confirms blend with the new chrome instead of looking like default web dialogs. - Load the Material Symbols Outlined font for the icons used by the TitleBar and per-page action buttons. - Ignore the local mockups/ folder. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Composite the existing bf-mica-bg gradient with a white overlay and the bf-glass-panel border / shadow / radius into a single class. Top-level pages adopting the glass-panel-as-window architecture use this instead of stacking two utilities or duplicating the full gradient inline, keeping our existing visual identity (warm orange/cream gradient, fonts) untouched while still going opaque so the desktop doesn't bleed through. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Extend RouteMeta with titleKey, titleIcon, windowWidth and windowHeight so each route self-describes the chrome it wants. TitleBar reads titleKey/titleIcon; an afterEach hook resizes the Tauri window via setSize() and observes the page root so content-driven height changes track reactively. Also export a resizeWindow() helper for pages that need to override the default at runtime (e.g. unauthenticated Settings). Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Add the titleBar.* tree (per-page chrome titles plus minimize / close tooltips) consumed by route.meta.titleKey, the new loginQr.enlarge / loginQr.copyQr / loginQr.copyQrSuccess keys for the QR enlarge + copy buttons, and remove the dead loginShell.* keys that the old brand header used. accountList.title and accountList.subtitle stay (the list page still renders its own header alongside the TitleBar). Disable warnHtmlMessage in createAppI18n so the warm-color HTML fragments embedded in some translations stop spamming the console. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Drop the old brand header (icon + heading + subline) since the TitleBar now communicates which page the user is on. Wrap the RouterView in a flex column so the glass card scrolls without overflowing the chrome. The TitleBar slot carries the region switcher (hidden on the picker itself) plus settings and about shortcuts so chrome real estate is consistent across login flow steps. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Add a zoom_in button that opens an overlay with a 360px QR for the user to scan from a phone held farther away, and an explicit content_copy button so the copy affordance is discoverable without right-clicking. The existing right-click-to-copy still works and the existing large-size action buttons (refresh, back, deeplink, game start) are kept as-is. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Promote the page root to bf-glass-window and pull the settings + about icons up into the TitleBar slot so the page chrome lines up with the rest of the app. The list header (title + subtitle) stays - the post-login landing still wants the explicit "pick an account" framing under the chrome, even though the TitleBar already announces the page. A scroll wrapper takes over from the old min-height/padded root so the account rows scroll inside the card rather than blowing past the rounded corners. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Same rewrap as AccountList: each page root is now a flex column with bf-glass-window for the visible card, the TitleBar mounts at the top, and a scroll wrapper holds the original content so long forms (Settings) and long histories (ManageAccount) stay inside the rounded card. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
Add a global tests/setup.ts that stubs the Tauri window and dpi modules and globally stubs TitleBar so per-page specs don't have to fight the chrome they don't care about. Refresh existing specs to match the chrome rewrap (LoginPage no longer renders the brand header, AccountList icons moved to the TitleBar slot, Iphone / Wallet icon stubs needed for the new game info bar). The drag-end harness now hands handleDragEnd a real DOM stub since the new SortableJS revert pass touches event.from / event.item, and the persist-failure case clones the populated fixture so the prior drag test cannot mutate the shared array. A FRONTEND_ONLY exception keeps the new loginQr.copyQrSuccess key out of the dead-key guard. Co-Authored-By: William Leung <61426712+lshw54@users.noreply.github.com>
…mize The borderless + transparent + non-resizable window introduced with the glass-panel shell no longer reliably emits Windows' WM_SIZE(SIZE_MINIMIZED, 0, 0), which `tray::handle_minimize_to_tray` listens for. As a result, clicking the TitleBar minimize button always minimized to the taskbar even with `minimize_to_tray = true`. Switch the TitleBar's minimize path from direct `appWindow.minimize()` to a new `minimize_main_window` Tauri command that reads the config and either hides+shows-tray or falls through to a plain minimize. The window-event fallback is kept for any OS-initiated minimize that still fires `Resized(0, 0)`. - tray: extract `hide_to_tray` helper (generic over Runtime); expose `is_minimize_to_tray_enabled`; add `TrayState` newtype so commands can reach the tray ID via managed state. - lib: register `TrayState` via `.manage()` so the setup/event closures and the new command share one Arc<Mutex<Option<TrayIconId>>>. - commands/system: add `minimize_main_window<R: Runtime>` with `system.window_not_found` / `system.minimize_failed` error codes. - commands/mod: collect the new command and add it to the bindings contract test. - bindings.ts: regenerated via `cargo run --example export_bindings`. - TitleBar.vue: call `commands.minimizeMainWindow()`; fallback to `appWindow.minimize()` on command error so the button never becomes a silent no-op.
The two D7 drag-end specs only assert against the Pinia store and the `commands.setConfig` spy — the mounted wrapper handle is never referenced. CI ESLint (`no-unused-vars`) blocked the PR on this. Replace `const wrapper = await ctx.mountIt()` with a plain `await ctx.mountIt()` so the component is still mounted (side effects, router, Pinia subscribers) but no dangling binding trips the lint.
CI Prettier check caught 5 files that were prettier-clean on `code` but got reformatted off-style by the PR pungin#228 cherry-picks and the earlier glass-panel restructure. Running `prettier --write` on just those files restores the committed style: - README.md - src/pages/AccountList.vue (template indentation after the glass-panel wrap) - src/stores/config.ts - src/windows/MapleTools.vue - tests/unit/pages/AccountList.spec.ts (from the previous unused-var fix) Pure whitespace / quote-style changes — no logic.
CI `cargo test` failed intermittently with:
Registry(Os { code: 1018, "Illegal operation attempted on a
registry key that has been marked for deletion." })
in `import_records_with_valid_json_writes_file_and_returns_records`
and `export_then_import_preserves_records_through_roundtrip`.
Root cause: `RegistryScope::drop` issued
`delete_subkey_non_recursive(TEST_REGISTRY_PARENT)` after deleting
its own child subkey. Because cargo runs test functions in parallel,
test A's Drop could start deleting the (now-empty) PARENT while
test B was mid-flight creating its own child under PARENT, producing
ERROR_KEY_DELETED (1018) on test B's registry ops.
Drop the parent-key cleanup. The sub-keys themselves are still
recursively deleted on per-test Drop, and `RegistryScope::new` also
pre-deletes its own sub-key before use, so no state leaks between
runs. The parent `HKCU\SOFTWARE\BEANFUN_NEXT_TEST` key remains
orphaned (empty) after the test suite exits — harmless since it's a
sandboxed test-only prefix that future runs reuse anyway.
Also remove the now-unused `delete_subkey_non_recursive` helper so
clippy `-D warnings` stays green on `dead_code`.
…rape - Update DEFAULT_USER_AGENT to full Chrome/130 string (HK portal rejects truncated UA) - Add session_key_from_hk_url() to extract otp1 from redirect URL (new HK portal redirect flow) - Re-scrape viewstate from TOTP POST response body (fresh viewstate needed for TOTP submission, not the GET viewstate) - Remove TOTP debug logging - Update session_key UA test to match new Chrome UA
- Add last_login_at optional field to Account schema + WireRecords - Record ISO 8601 timestamp on successful login in account store - Display formatted last login time in ManageAccount table - Update bindings.ts with new Account field - Fix test Account/WireRecords literals for new field
- Use separate TW/HK image base URLs instead of unified images.beanfun.com/GameZone/
- LoginTotp: back button, simplified layout, single submit button - IdPassForm: per-region account key, saved accounts dropdown, region-aware prefill
…dow heights - Close GamePass window on region toggle - Add requiresAuth to ManageAccount route - Adjust id-pass window height to 520 - Add currentRegion computed to LoginRegionSelection - RouterView keyed by currentRegion for proper re-render - Update router tests for requiresAuth change
- Hide 'App Gash Recharge' button for HK region (WPF parity) - Add accountList.title/subtitle i18n keys for zh-TW/zh-CN/en - Fix duplicate TitleBar import - Fix SortableJS row markup (missing button wrapper)
wry's set_cookie strips the leading dot from cookie domains because the cookie crate's domain() getter normalises it. WebView2's CreateCookie treats the domain literally — without the dot, suffix cookies (Domain=.beanfun.com) only match the apex host, not subdomains like bfweb.hk.beanfun.com. This adds cookie_native.rs which bypasses wry entirely and calls ICoreWebView2CookieManager::CreateCookie + AddOrUpdateCookie via the COM interface directly (same API WPF uses), preserving the leading dot for proper subdomain matching. Also adds dot-prefix logic to seed_webview_cookies_from_client for the GamePass flow, and adds webview2-com-sys + windows-core deps.
5428ecd to
adfafa1
Compare
- Check file permissions before writing; return PermissionDenied if read-only (users lock Config.xml to preserve settings across updates) - Remove the remove_file call on parse failure — corrupted files should not be silently deleted - Add get_value_sync for non-async contexts
- Read disableHardwareAcceleration from Config.xml at startup - Set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-gpu before WebView2 runtime init (same as WPF's AdditionalBrowserArguments) - Restore the Settings checkbox (was incorrectly hidden)
Switch from embedBootstrapper to offlineInstaller so the NSIS installer bundles the full WebView2 runtime and works without internet access. Helps users on stripped-down Windows 10 without Edge pre-installed.
The legacy tw.images.beanfun.com and hk.images.beanfun.com hosts are dead. Beanfun now serves all game images from images.beanfun.com/GameZone/. Aligns frontend imageUrl() with the Rust image_base_url() that already uses the unified host.
- Call game.clearGameData() on manual logout (was only called on session-expired). Fixes stale TW game images showing after logout → HK re-login. - imageUrl: return empty string for empty name (prevents 403 from bare base URL). TW uses images.beanfun.com/GameZone/, HK uses legacy hk.images.beanfun.com.
Additional fixes (issues collected from 巴哈 user reports)
|
Beanfun pages use target=_blank and window.open() for navigation (e.g. Gash recharge buttons). WebView2 blocks popup windows by default, so these clicks silently fail. Adds a native ICoreWebView2 NewWindowRequested COM event handler via webview2-com that redirects popup requests to navigate within the same window — same as WPF WebBrowser.xaml.cs: wb_Main.CoreWebView2.Navigate(e.Uri); e.Handled = true; New dep: webview2-com 0.38 (same version wry uses).
The previous `(?m)^version\s*=\s*"[\d.]+"` pattern in the version-bump step matched every line starting with `version`, including the `version = "0.61"` inside `[target.'cfg(windows)'.dependencies.wv2-windows-core]` introduced by pungin#230. The release workflow rewrote that line to the package version, causing cargo to look up `windows-core = "^5.9.3"` and fail with `failed to select a version for the requirement` (see run pungin#20 / job 72225904016). Scope the replacement to the [package] section only by anchoring on `[package]` and stopping at the next section header via a `(?!\r?\n\[)` negative lookahead. CRLF/LF endings are both handled.
…ck escape hatch
LoginRegionSelection.vue had two competing auto-redirect mechanisms:
1. `onMounted` (legacy): if `loginRegion` was saved, immediately
`router.replace('/login/id-pass')`. Did NOT check `route.query.pick`,
did NOT consult `loginMethod` (always landed on id-pass).
2. `watch(() => config.loaded, ..., { immediate: true })` added by
commit 24a07af: same redirect intent, but correctly honours the
`?pick=1` escape hatch and routes to `/login/qr` when
`loginMethod === '1'` on TW.
Because `onMounted` ran synchronously before the watcher's config-loaded
trigger, every back-button navigation from a login form (which appends
`?pick=1` per 24a07af's design) got bounced straight back to id-pass —
the picker became unreachable after first launch, and QR-mode users
were silently downgraded to id-pass on every return trip.
This looks like a leftover from cherry-picking 24a07af in PR pungin#230: the
new watcher was added but the old `onMounted` was never removed.
Drop the `onMounted` block (and its now-unused import). The watcher
with `immediate: true` already covers first-mount, and its logic is
strictly a superset of the deleted block.
Add regression tests covering all five branches of the redirect
decision so the duplication can't silently come back:
- saved TW + default loginMethod → /login/id-pass
- saved TW + loginMethod=1 → /login/qr
- saved HK + loginMethod=1 → /login/id-pass (HK has no QR)
- saved region + ?pick=1 → stays on picker
- no saved region → stays on picker



What
Supersedes #229. Includes all changes from #229 (glass-panel UI refresh, minimize-to-tray fix, SortableJS, Gash recharge buttons) plus HK login fixes, UI improvements, and a critical fix for in-app browser cookie seeding.
HK login
session_key_from_hk_url()— HK portal now redirects tologinform_newBF.aspx?otp1={skey}, so we grab the key from the URL first, body scrape as fallback__VIEWSTATEfrom the TOTP POST response — the POST carries a fresh viewstate, reusing the GET one causes ASP.NET validation errorsUI / account stuff
last_login_aton Account — timestamp recorded on login, shown in ManageAccountrequiresAuthbtn_Depositefor non-TW)accountList.title/subtitlei18n keysCookie seeding fix (the big one)
wry's
set_cookieuses thecookiecrate'sdomain()getter which strips the leading dot. SoCreateCookie("bfWebToken", …, "beanfun.com", "/")only matches the exact host — notbfweb.hk.beanfun.com. Every in-app browser page hitrelogin_msg_slave.htm.Fix: new
cookie_native.rsthat callsICoreWebView2CookieManager::CreateCookie+AddOrUpdateCookiethrough the COM API directly (same thing WPF does), keeping.beanfun.comintact.target="_blank"/window.open()for navigation. WebView2 blocks popups by default. Added a nativeNewWindowRequestedCOM event handler that redirects popup requests to navigate within the same window, matching WPF'sCoreWebView2_NewWindowRequestedhandler.New Windows-only deps:
webview2-com-sys 0.38(already a transitive dep from wry),windows-core 0.61.Merge notes (from #229)
bf-glass-panel→bf-glass-windowminimizeMainWindowcommand in TitleBarcopyQrImageChecks
cargo test --libvitest runnpm run lintprettier --checkvue-tsc --noEmitcargo fmt --checkcargo clippy -D warningsManual testing