feat: implement headless LS authentication via state sync

Reverse-engineered the UnifiedStateSyncUpdate protocol:
- initial_state field is bytes (not string), contains serialized Topic proto
- Map key for OAuth is 'oauthTokenInfoSentinelKey'
- Row.value is base64-encoded OAuthTokenInfo protobuf
- OAuthTokenInfo includes access_token, token_type, expiry (Timestamp)
- Set far-future expiry (2099) to prevent token expiry errors

Also fixed:
- PushUnifiedStateSyncUpdate returns proper empty proto response
- Stream keep-alive avoids sending empty envelopes (LS rejects nil updates)
- uss-enterprisePreferences topic handled (empty initial state)
This commit is contained in:
Nikketryhard
2026-02-15 21:40:35 -06:00
parent 4e4d8e9474
commit 6a07786c4e
6 changed files with 936 additions and 59 deletions

View File

@@ -35,9 +35,17 @@ pub async fn warmup_sequence(backend: &Backend) {
];
for (method, body) in calls {
match backend.call_json(method, body).await {
Ok((status, _)) => debug!("Warmup {method}: {status}"),
Err(e) => warn!("Warmup {method} failed: {e}"),
// Timeout per call — in headless mode, the LS can't reach Google's API
// so these would hang forever without a timeout. Warmup is best-effort.
match tokio::time::timeout(
Duration::from_secs(5),
backend.call_json(method, body),
)
.await
{
Ok(Ok((status, _))) => debug!("Warmup {method}: {status}"),
Ok(Err(e)) => warn!("Warmup {method} failed: {e}"),
Err(_) => warn!("Warmup {method} timed out"),
}
// Small delay between calls — real webview doesn't blast them instantly
let delay = rand::thread_rng().gen_range(50..200);