Skip to content

fix: HK login, UI improvements, and native WebView2 cookie seeding (supersedes #229)#230

Merged
YCC3741 merged 34 commits intopungin:codefrom
lshw54:fix/hk-login-and-cookie-seeding
Apr 20, 2026
Merged

fix: HK login, UI improvements, and native WebView2 cookie seeding (supersedes #229)#230
YCC3741 merged 34 commits intopungin:codefrom
lshw54:fix/hk-login-and-cookie-seeding

Conversation

@lshw54
Copy link
Copy Markdown
Collaborator

@lshw54 lshw54 commented Apr 20, 2026

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

  • Full Chrome/130 UA — HK portal rejects the truncated one fix: custom titlebar + glass-panel shell + minimize-to-tray regression #229 shipped
  • New session_key_from_hk_url() — HK portal now redirects to loginform_newBF.aspx?otp1={skey}, so we grab the key from the URL first, body scrape as fallback
  • Re-scrape __VIEWSTATE from the TOTP POST response — the POST carries a fresh viewstate, reusing the GET one causes ASP.NET validation errors
  • Removed leftover TOTP debug logging

UI / account stuff

  • last_login_at on Account — timestamp recorded on login, shown in ManageAccount
  • Region-aware game image URLs (separate TW/HK base URLs, matching WPF)
  • LoginTotp: back button, cleaner layout
  • IdPassForm: per-region account key, saved accounts dropdown
  • Close GamePass window on region toggle
  • ManageAccount now behind requiresAuth
  • RouterView keyed by region so it re-renders properly on switch
  • Hide "App Gash Recharge" for HK (WPF hides btn_Deposite for non-TW)
  • Added accountList.title / subtitle i18n keys

Cookie seeding fix (the big one)

wry's set_cookie uses the cookie crate's domain() getter which strips the leading dot. So CreateCookie("bfWebToken", …, "beanfun.com", "/") only matches the exact host — not bfweb.hk.beanfun.com. Every in-app browser page hit relogin_msg_slave.htm.

Fix: new cookie_native.rs that calls ICoreWebView2CookieManager::CreateCookie + AddOrUpdateCookie through the COM API directly (same thing WPF does), keeping .beanfun.com intact.

  • In-app browser buttons not clickable (HK Gash recharge page) — beanfun pages use target="_blank" / window.open() for navigation. WebView2 blocks popups by default. Added a native NewWindowRequested COM event handler that redirects popup requests to navigate within the same window, matching WPF's CoreWebView2_NewWindowRequested handler.

New Windows-only deps: webview2-com-sys 0.38 (already a transitive dep from wry), windows-core 0.61.

Merge notes (from #229)

Checks

cargo test --lib 733 pass
vitest run 584 pass / 40 files
npm run lint clean
prettier --check clean
vue-tsc --noEmit clean
cargo fmt --check clean
cargo clippy -D warnings clean

Manual testing

  • HK id-pass + TOTP login works
  • 儲值與購點 / 會員中心 opens with logged-in session
  • 兌換 beanfun!App 樂豆點 hidden for HK
  • ManageAccount shows last login time
  • Region toggle closes GamePass window
  • LoginTotp back button works

YCC3741 and others added 26 commits April 20, 2026 19:53
…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.
@lshw54 lshw54 force-pushed the fix/hk-login-and-cookie-seeding branch from 5428ecd to adfafa1 Compare April 20, 2026 16:39
lshw54 added 3 commits April 21, 2026 00:43
- 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)
lshw54 added 4 commits April 21, 2026 01:27
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.
@lshw54
Copy link
Copy Markdown
Collaborator Author

lshw54 commented Apr 20, 2026

Additional fixes (issues collected from 巴哈 user reports)

  • Stripped-down Win10 without Edge can't launch v5.9.2 — switched installer webviewInstallMode from embedBootstrapper to offlineInstaller so the NSIS installer bundles the full WebView2 runtime. No internet or pre-installed Edge required.image

  • Config.xml set to read-only gets overwritten / deleted on startup — users lock Config.xml to preserve settings (region, update preferences, etc.) across reinstalls. set_value_blocking now checks file permissions before writing and skips the write if read-only. Also removed the remove_file call that deleted corrupted Config.xml — a broken file should not be silently nuked.image

  • "Disable hardware acceleration" checkbox breaks features / merges GP accounts — the setting was written to Config.xml but never actually applied in the SPA. Now reads the flag at startup and sets WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-gpu --disable-gpu-compositing before WebView2 init, matching WPF's AdditionalBrowserArguments behaviour.image

  • Game icons break after logout → re-login with different region — manual logout was not clearing the game store, so switching TW→HK reused stale TW image data. Now calls game.clearGameData() on logout. Also fixed region-aware image base URLs (TW uses images.beanfun.com/GameZone/, HK uses legacy hk.images.beanfun.com).

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).
@YCC3741 YCC3741 merged commit 59b98ab into pungin:code Apr 20, 2026
2 checks passed
lshw54 pushed a commit to lshw54/Beanfun that referenced this pull request Apr 22, 2026
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.
lshw54 pushed a commit to lshw54/Beanfun that referenced this pull request Apr 22, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants