fix(cliproxy): clear stale cleanup timer and fix session/tunnel edge cases

- Stop cleanup interval when all auth sessions are removed (unregister,
  cancel, cancelAll) so the Node event loop isn't held alive.
- getActiveSessionForProvider now returns the most recent session by
  startedAt instead of the first map entry.
- HTTPS tunnel includes port in Host header when non-443.
This commit is contained in:
Tam Nhu Tran
2026-04-29 18:13:15 -04:00
parent 6633bf456b
commit 20c2005df9
2 changed files with 23 additions and 11 deletions
+17 -9
View File
@@ -39,14 +39,18 @@ function startCleanupInterval(): void {
authSessionEvents.emit('session:expired', sessionId);
}
}
// Stop interval if no active sessions
if (activeSessions.size === 0 && cleanupInterval) {
clearInterval(cleanupInterval);
cleanupInterval = null;
}
stopCleanupIfEmpty();
}, 60000); // Check every minute
}
/** Clear the cleanup interval when no sessions remain. */
function stopCleanupIfEmpty(): void {
if (activeSessions.size === 0 && cleanupInterval) {
clearInterval(cleanupInterval);
cleanupInterval = null;
}
}
/**
* Register an active OAuth session
*/
@@ -82,6 +86,7 @@ export function attachProcessToSession(sessionId: string, process: ChildProcess)
export function unregisterAuthSession(sessionId: string): void {
activeSessions.delete(sessionId);
authSessionEvents.emit('session:ended', sessionId);
stopCleanupIfEmpty();
}
/**
@@ -101,6 +106,7 @@ export function cancelAuthSession(sessionId: string): boolean {
activeSessions.delete(sessionId);
authSessionEvents.emit('session:cancelled', sessionId);
stopCleanupIfEmpty();
return true;
}
@@ -112,15 +118,16 @@ export function getActiveSession(sessionId: string): ActiveAuthSession | null {
}
/**
* Get active session for a provider (most recent)
* Get active session for a provider (most recent by startedAt)
*/
export function getActiveSessionForProvider(provider: string): ActiveAuthSession | null {
let latest: ActiveAuthSession | null = null;
for (const session of activeSessions.values()) {
if (session.provider === provider) {
return session;
if (session.provider === provider && (!latest || session.startedAt > latest.startedAt)) {
latest = session;
}
}
return null;
return latest;
}
/**
@@ -145,5 +152,6 @@ export function cancelAllSessionsForProvider(provider: string): number {
count++;
}
}
stopCleanupIfEmpty();
return count;
}
+6 -2
View File
@@ -162,8 +162,12 @@ export class HttpsTunnelProxy {
headers[key] = value;
}
// Set correct host header for remote
headers['Host'] = this.config.remoteHost;
// Set correct host header for remote (include port when non-default)
if (this.config.remotePort === 443) {
headers['Host'] = this.config.remoteHost;
} else {
headers['Host'] = `${this.config.remoteHost}:${this.config.remotePort}`;
}
// Inject Authorization header if not present but authToken is configured
// This is a fallback - normally the client (CodexReasoningProxy) forwards the header