[client] Expose the SSO auth session to the Android binding

The Android client had no way to see or refresh the peer's SSO session:
the core tracked the deadline and published expiry warnings, but none of
it was exported, so an expired session surfaced only as a raw error
string from the engine run loop.

Mirror the surface the daemon serves its tray:

- Status() and SessionExpiresAtUnix() report the run-loop status label
  and the tracked deadline, the two values StatusResponse carries.
- StateChangeListener signals state changes (payload-free, consumers
  re-read the getters) and forwards the session-expiry warnings from
  the engine's watcher, filtered out of the shared event stream.
- ExtendAuthSession() runs the interactive SSO flow and refreshes the
  deadline without touching the tunnel, with CancelExtendAuthSession()
  for an abandoned browser round-trip — its PKCE wait would otherwise
  hold the loopback port until it timed out and block every retry.
- DismissSessionWarning() suppresses the final warning.

Status() latches NeedsLogin: the run loop keeps its status in a
per-run context state that a restart replaces with a fresh Idle one, so
an engine restart would otherwise erase the fact that the peer still
needs to log in. Only a successful login or extend clears it.
This commit is contained in:
Zoltán Papp
2026-07-28 16:47:53 +02:00
parent 4acbe2670a
commit d00068bd89
2 changed files with 266 additions and 0 deletions
+15
View File
@@ -8,6 +8,7 @@ import (
"os"
"slices"
"sync"
"sync/atomic"
"time"
"golang.org/x/exp/maps"
@@ -75,6 +76,17 @@ type Client struct {
connectClient *internal.ConnectClient
config *profilemanager.Config
cacheDir string
stateChangeMu sync.Mutex
stateChangeSubID string
eventSub *peer.EventSubscription
// Latched "the server wants an interactive login": survives the engine
// restarts that replace the run loop's context state. See Client.Status.
loginRequired atomic.Bool
extendMu sync.Mutex
extendCancel context.CancelFunc
}
func (c *Client) setState(cfg *profilemanager.Config, cacheDir string, cc *internal.ConnectClient) {
@@ -148,6 +160,9 @@ func (c *Client) Run(platformFiles PlatformFiles, urlOpener URLOpener, isAndroid
if err != nil {
return err
}
// This path runs the interactive SSO flow, so reaching here means the peer
// is authenticated again — release the latch Status() reports from.
c.loginRequired.Store(false)
// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)