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>
170 lines
6.0 KiB
PowerShell
170 lines
6.0 KiB
PowerShell
<#
|
|
.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
|
|
}
|
|
}
|