Phase 5/6 polish: end-session flow, notif sound on API 33+, Xendit webview
Customer end-of-session (figma §6):
- PricingBottomSheet: ghost "cukup, akhiri sesi" CTA + dedup divider
- chat_screen._runEndSessionFlow chains ConfirmEndStep1 → ConfirmEndStep2
→ ClosingMessageSheet (or "lewati saja" → close + /home). The four
popup/sheet widgets already existed; this commit just wires them
- showModalBottomSheet: showDragHandle=false to suppress the Material 3
auto-injected handle that was stacking with our own pill
Notification sound on API 33+:
- Bump channel halobestie_chat_v1 → halobestie_chat_v2, created from
native Kotlin in MainActivity.kt with AudioAttributes contentType
CONTENT_TYPE_SONIFICATION. flutter_local_notifications' default of
CONTENT_TYPE_UNKNOWN was causing Android 13 to silently drop audio
focus while the notification still posted (isNoisy=true). Both apps
- Backend FCM payload channelId updated to v2
- AndroidManifest meta-data: default_notification_icon + color → brand
silhouette tinted pink instead of generic Android bell. Both apps
Customer pairing reliability:
- pairing_notifier: applyPairedFromPush({sessionId, mitraName}) unsticks
searching screen when WS push failed and FCM/active-session-poll is
the first signal. Idempotent across PairingSearchingData,
PairingTargetedWaitingData, PairingErrorData (covers ALREADY_ACTIVE)
- notification_service: dispatches every FCM data payload to an
onDataMessage callback (foreground + tap + cold-start). main.dart
wires that to applyPairedFromPush on type=='paired'. Foreground
'paired' no longer renders a local banner — screen self-advances
- main.dart activeSession listener also calls applyPairedFromPush when
a session appears server-side while pairing is in a waiting state.
Covers stale ALREADY_ACTIVE recovery without a full page refresh
Auth refresh token race:
- auth_notifier._refreshFromStorage shares a single in-flight Future
across all callers (Auth.build + 401-retry path). Backend rotates
refresh tokens, so concurrent callers using the same stored token
would race → loser 401s → catch wipes flutter_secure_storage → user
appears logged out after kill+reopen
Polish:
- method_pick_screen: resizeToAvoidBottomInset=false — prevents the
one-frame overflow when entering with the previous screen's keyboard
still animating out
- bestie_history: BestieHistoryItem now carries `status` (backend
already returns it). Removed _rawHistoryProvider that fetched the
same endpoint just to read status; the two providers could go out
of sync mid-rebuild and throw RangeError(length) on indexing
Xendit Stage 8 (carried from WIP):
- xendit_checkout_screen: embedded webview hosting Xendit's invoice
page (intercepts halobestie:// deeplink + return-page URLs for
deterministic pop)
- waiting_payment_screen: auto-pushes the webview when the backend
payload includes xendit_invoice_url; spinner card + "Buka ulang
halaman pembayaran" CTA for the QR-fallback path
- pubspec: webview_flutter ^4.13.0
Maestro infra:
- subflows/onboarding_returning_user: drop the "Mulai" carousel wait
(splash auto-advances since 2026-05-26); tap phone-field hint
instead of point; drop hideKeyboard (sends BACK → /home when the
IME isn't actually up)
- New flow ts-customer-06-01-end_session_via_timeup_sheet: drives
the full path to the chat-expired banner. Last step blocked by a
Maestro+Flutter gesture quirk on the perpanjang ElevatedButton
(raw `adb input tap` works at the same coords). Documented in
memory; deeplink fixture or manual verify recommended
- ChatExpiredBanner button wrapped with Semantics(identifier:
'chat_extend_button', button: true, onTap: …) — good hygiene for
future tests even though it doesn't fix the dadb tap issue
.dev/: tracked wsl_emulator_bridge.ps1 + wsl_tcp_relay.py for
Maestro-on-WSL setup (Windows-side netsh portproxy + WSL-side
loopback relays). Both referenced from existing CLAUDE.md notes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
169
.dev/wsl_emulator_bridge.ps1
Normal file
169
.dev/wsl_emulator_bridge.ps1
Normal file
@@ -0,0 +1,169 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Toggle a Windows-side TCP bridge so Maestro (running in WSL) can reach the
|
||||
Android emulators' adb ports.
|
||||
|
||||
.DESCRIPTION
|
||||
Android emulators on the Windows host bind their adb ports (5555, 5557) to
|
||||
127.0.0.1 only. Maestro's bundled `dadb` connects directly to that port —
|
||||
not via the adb server — so `ADB_SERVER_SOCKET` is irrelevant. To run
|
||||
`maestro test` from inside WSL, we expose those ports on the WSL-facing
|
||||
interface and let the matching WSL-side relay (.dev/wsl_tcp_relay.py)
|
||||
bridge 127.0.0.1 in WSL → <gateway> on Windows.
|
||||
|
||||
Heads-up: idle `netsh portproxy` entries with no live target have been
|
||||
reported to occasionally pin CPU on iphlpsvc. Keep these rules OFF when
|
||||
you're not running Maestro from WSL — that is the whole point of this
|
||||
script.
|
||||
|
||||
.PARAMETER Action
|
||||
up Add port-proxy entries + firewall rules.
|
||||
down Remove them.
|
||||
status Print current portproxy + matching firewall rules.
|
||||
|
||||
.PARAMETER ListenAddress
|
||||
Optional. Defaults to the IPv4 of the `vEthernet (WSL)` adapter — the IP
|
||||
that WSL2 sees as its default gateway. Override only if your WSL adapter
|
||||
is named differently.
|
||||
|
||||
.PARAMETER Ports
|
||||
Optional. Defaults to 5554,5555,5556,5557 — the console + adb ports for
|
||||
emulator-5554 (5554 console, 5555 adb) and emulator-5556 (5556 console,
|
||||
5557 adb). Maestro / dadb need both: the console port for authentication
|
||||
and the adb port for the actual protocol.
|
||||
|
||||
.EXAMPLE
|
||||
# From an elevated PowerShell, before `maestro test ...` in WSL:
|
||||
.\wsl_emulator_bridge.ps1 up
|
||||
|
||||
.EXAMPLE
|
||||
# When done testing — tear it back down to avoid the CPU-pin risk:
|
||||
.\wsl_emulator_bridge.ps1 down
|
||||
|
||||
.EXAMPLE
|
||||
.\wsl_emulator_bridge.ps1 status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[ValidateSet('up', 'down', 'status')]
|
||||
[string]$Action,
|
||||
|
||||
[string]$ListenAddress,
|
||||
|
||||
[int[]]$Ports = @(5554, 5555, 5556, 5557)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$FirewallRulePrefix = 'WSL adb bridge'
|
||||
|
||||
function Assert-Admin {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'This script must be run from an elevated PowerShell (Run as administrator).'
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-WslGatewayIp {
|
||||
param([string]$Override)
|
||||
if ($Override) { return $Override }
|
||||
|
||||
# WSL2 always creates a `vEthernet (WSL)` adapter on the Windows host.
|
||||
# Its IPv4 address is what WSL2 sees as `default via ...` in `ip route`.
|
||||
$candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.InterfaceAlias -like 'vEthernet (WSL*)' -and $_.PrefixOrigin -ne 'WellKnown'
|
||||
} |
|
||||
Sort-Object -Property InterfaceMetric
|
||||
|
||||
if (-not $candidates) {
|
||||
throw "Couldn't find a `vEthernet (WSL)` adapter. Pass -ListenAddress explicitly."
|
||||
}
|
||||
return $candidates[0].IPAddress
|
||||
}
|
||||
|
||||
function Get-PortProxyEntries {
|
||||
$out = & netsh interface portproxy show v4tov4 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "netsh interface portproxy show v4tov4 failed: $out"
|
||||
}
|
||||
return $out
|
||||
}
|
||||
|
||||
function Invoke-PortProxyUp {
|
||||
param([string]$Addr, [int]$Port)
|
||||
& netsh interface portproxy add v4tov4 `
|
||||
listenaddress=$Addr listenport=$Port `
|
||||
connectaddress=127.0.0.1 connectport=$Port | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to add portproxy for $Addr`:$Port (exit $LASTEXITCODE)"
|
||||
}
|
||||
|
||||
$ruleName = "$FirewallRulePrefix $Port"
|
||||
$existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
if (-not $existing) {
|
||||
New-NetFirewallRule -DisplayName $ruleName `
|
||||
-Direction Inbound -Action Allow -Protocol TCP `
|
||||
-LocalPort $Port -LocalAddress $Addr | Out-Null
|
||||
}
|
||||
Write-Host " + $Addr`:$Port -> 127.0.0.1:$Port (firewall: $ruleName)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Invoke-PortProxyDown {
|
||||
param([string]$Addr, [int]$Port)
|
||||
& netsh interface portproxy delete v4tov4 `
|
||||
listenaddress=$Addr listenport=$Port 2>$null | Out-Null
|
||||
|
||||
$ruleName = "$FirewallRulePrefix $Port"
|
||||
$rule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
if ($rule) { Remove-NetFirewallRule -DisplayName $ruleName }
|
||||
Write-Host " - $Addr`:$Port removed" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
switch ($Action) {
|
||||
'up' {
|
||||
Assert-Admin
|
||||
$addr = Resolve-WslGatewayIp -Override $ListenAddress
|
||||
Write-Host "Bringing WSL adb bridge UP on $addr ..." -ForegroundColor Cyan
|
||||
foreach ($p in $Ports) { Invoke-PortProxyUp -Addr $addr -Port $p }
|
||||
Write-Host ''
|
||||
Write-Host 'Remember to take it DOWN when you are done:' -ForegroundColor DarkGray
|
||||
Write-Host ' .\wsl_emulator_bridge.ps1 down' -ForegroundColor DarkGray
|
||||
}
|
||||
'down' {
|
||||
Assert-Admin
|
||||
# If user didn't pass -ListenAddress, try to clean up whichever address
|
||||
# is currently bound. Walk the existing portproxy entries.
|
||||
$entries = Get-PortProxyEntries
|
||||
$matched = @()
|
||||
foreach ($p in $Ports) {
|
||||
$regex = "^\s*(\d+\.\d+\.\d+\.\d+)\s+$p\s+"
|
||||
foreach ($line in $entries) {
|
||||
if ($line -match $regex) {
|
||||
$matched += [pscustomobject]@{ Addr = $matches[1]; Port = $p }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $matched -and $ListenAddress) {
|
||||
foreach ($p in $Ports) {
|
||||
$matched += [pscustomobject]@{ Addr = $ListenAddress; Port = $p }
|
||||
}
|
||||
}
|
||||
if (-not $matched) {
|
||||
Write-Host 'No matching portproxy entries found. Nothing to remove.' -ForegroundColor DarkGray
|
||||
break
|
||||
}
|
||||
Write-Host 'Tearing WSL adb bridge DOWN ...' -ForegroundColor Cyan
|
||||
foreach ($m in $matched) { Invoke-PortProxyDown -Addr $m.Addr -Port $m.Port }
|
||||
}
|
||||
'status' {
|
||||
Write-Host 'Active portproxy entries:' -ForegroundColor Cyan
|
||||
Get-PortProxyEntries | ForEach-Object { Write-Host " $_" }
|
||||
Write-Host ''
|
||||
Write-Host 'Matching firewall rules:' -ForegroundColor Cyan
|
||||
Get-NetFirewallRule -DisplayName "$FirewallRulePrefix *" -ErrorAction SilentlyContinue |
|
||||
Select-Object DisplayName, Enabled, Direction, Action |
|
||||
Format-Table -AutoSize | Out-String | Write-Host
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user