(currentSessionId: string | null)
| 498 | } |
| 499 | |
| 500 | async listSessions(currentSessionId: string | null): Promise<ServerAuthSessionView[]> { |
| 501 | await using _lock = await this.sessionsMutex.acquire(); |
| 502 | const data = await this.loadPersistedSessionsLocked(); |
| 503 | |
| 504 | const now = Date.now(); |
| 505 | const unexpiredSessions = data.sessions.filter((session) => { |
| 506 | const ageMs = now - session.createdAtMs; |
| 507 | return ( |
| 508 | Number.isFinite(session.createdAtMs) && |
| 509 | Number.isFinite(ageMs) && |
| 510 | ageMs < SERVER_AUTH_SESSION_MAX_AGE_MS |
| 511 | ); |
| 512 | }); |
| 513 | |
| 514 | if (unexpiredSessions.length !== data.sessions.length) { |
| 515 | const removedCount = data.sessions.length - unexpiredSessions.length; |
| 516 | data.sessions = unexpiredSessions; |
| 517 | |
| 518 | try { |
| 519 | await this.savePersistedSessionsLocked(data); |
| 520 | } catch (error) { |
| 521 | // Listing sessions should remain available even if cleanup persistence fails. |
| 522 | log.warn("Failed to persist expired server auth session cleanup", { |
| 523 | removedCount, |
| 524 | error: getErrorMessage(error), |
| 525 | }); |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | return unexpiredSessions |
| 530 | .map((session) => ({ |
| 531 | id: session.id, |
| 532 | label: session.label ?? buildSessionLabel(session.userAgent), |
| 533 | createdAtMs: session.createdAtMs, |
| 534 | lastUsedAtMs: session.lastUsedAtMs, |
| 535 | isCurrent: currentSessionId != null && currentSessionId === session.id, |
| 536 | })) |
| 537 | .sort((a, b) => b.lastUsedAtMs - a.lastUsedAtMs); |
| 538 | } |
| 539 | |
| 540 | async revokeSession(sessionId: string): Promise<boolean> { |
| 541 | const normalizedSessionId = normalizeOptionalString(sessionId); |
no test coverage detected