diff --git a/backend/internal/service/maintenance.go b/backend/internal/service/maintenance.go index 1f4ff41..dd831dd 100644 --- a/backend/internal/service/maintenance.go +++ b/backend/internal/service/maintenance.go @@ -63,6 +63,10 @@ func NewMaintenanceService(tokens *repo.TokenRepository, tokenSvc *TokenService, func (m *MaintenanceService) Run(ctx context.Context) { ticker := time.NewTicker(m.interval) defer ticker.Stop() + // The leonardo session keep-alive gets its own loop: one tick() can take tens + // of minutes (219 adobe cookie profiles alone), which would stretch a 5-minute + // keep-alive to the tick's real duration. + go m.runLeonardoKeepalive(ctx) m.tick(ctx) for { select { @@ -74,6 +78,25 @@ func (m *MaintenanceService) Run(ctx context.Context) { } } +// runLeonardoKeepalive re-checks every minute which leonardo accounts are due for +// a session renewal (the 5-minute due-ness itself is read per account from the DB, +// so a restart can't skip one). +func (m *MaintenanceService) runLeonardoKeepalive(ctx context.Context) { + if m.tokenSvc == nil { + return + } + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + for { + m.tokenSvc.RefreshLeonardoSessions(ctx) + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + // syncRecoveredQuota re-probes each just-recovered account so its displayed // balance reflects the post-reset value (these providers only sync quota when // accessed). krea additionally needs /app (Activate) to actually grant the daily @@ -165,12 +188,6 @@ func (m *MaintenanceService) tick(ctx context.Context) { // means the membership lapsed → disable+dead; otherwise re-sync the // credits balance and 恢复时间 (from the credits' weekly reset). m.tokenSvc.RefreshGrokLiveness(ctx) - // 1f. Keep leonardo's better-auth session rolling: get-session extends the - // session and rotates the cookie, so re-minting stops a dormant - // account's cookie from lapsing (a lapsed one can only be fixed by - // re-importing a browser cookie). Due-ness is read per account from - // meta["session_kept_at"], so a restart can't skip a renewal. - m.tokenSvc.RefreshLeonardoSessions(ctx) } // 2. Auto-renew Adobe cookies whose refresh interval has elapsed. diff --git a/backend/internal/service/tokens.go b/backend/internal/service/tokens.go index 9cfe6a8..cd9b42e 100644 --- a/backend/internal/service/tokens.go +++ b/backend/internal/service/tokens.go @@ -172,6 +172,7 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { } due := time.Now().Add(-leonardoKeepaliveEvery).Unix() proxied := false + renewed, failed := 0, 0 for i := range items { a := items[i] if a.Dead || a.Status == "disabled" || strings.TrimSpace(a.Value) == "" { @@ -188,6 +189,7 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { _, err := s.leonardo.ProbeSession(callCtx, a.Value) if err != nil { cancel() + failed++ if errors.Is(err, leonardo.ErrAuth) { log.Printf("leonardo %s: session keepalive auth failure (%v)", a.ID, err) } @@ -202,6 +204,10 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { fields["meta"] = meta _, _ = s.tokens.Update(callCtx, "leonardo", a.ID, fields) cancel() + renewed++ + } + if renewed > 0 || failed > 0 { + log.Printf("leonardo: session keepalive renewed %d, failed %d", renewed, failed) } }() }