import { v4 as uuidv4 } from 'uuid';
import type { Client } from 'ssh2';
import { createSSHConnection, closeConnection, isClientConnected } from './connection.js';
import type { SSHConnectionConfig, SSHSession, ShellSession } from '../types.js';
class SessionManager {
private sessions: Map<string, SSHSession> = new Map();
async createSession(config: SSHConnectionConfig): Promise<SSHSession> {
const client = await createSSHConnection(config);
const session: SSHSession = {
id: uuidv4(),
config,
client,
connected: true,
createdAt: new Date(),
lastUsedAt: new Date(),
shells: new Map(),
};
// Set up disconnect handler
client.on('close', () => {
session.connected = false;
});
client.on('end', () => {
session.connected = false;
});
this.sessions.set(session.id, session);
return session;
}
getSession(sessionId: string): SSHSession | undefined {
const session = this.sessions.get(sessionId);
if (session) {
session.lastUsedAt = new Date();
// Verify connection is still active
if (!isClientConnected(session.client)) {
session.connected = false;
}
}
return session;
}
getClient(sessionId: string): Client | undefined {
const session = this.getSession(sessionId);
if (session && session.connected) {
return session.client;
}
return undefined;
}
listSessions(): Array<{
id: string;
host: string;
username: string;
connected: boolean;
createdAt: Date;
lastUsedAt: Date;
shellCount: number;
}> {
return Array.from(this.sessions.values()).map((session) => ({
id: session.id,
host: session.config.host,
username: session.config.username,
connected: session.connected && isClientConnected(session.client),
createdAt: session.createdAt,
lastUsedAt: session.lastUsedAt,
shellCount: session.shells.size,
}));
}
closeSession(sessionId: string): boolean {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
// Close all shells first
for (const shell of session.shells.values()) {
try {
shell.channel.close();
} catch {
// Ignore errors
}
}
session.shells.clear();
// Close the connection
closeConnection(session.client);
session.connected = false;
this.sessions.delete(sessionId);
return true;
}
addShell(sessionId: string, shell: ShellSession): boolean {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
session.shells.set(shell.id, shell);
return true;
}
getShell(sessionId: string, shellId: string): ShellSession | undefined {
const session = this.sessions.get(sessionId);
if (!session) {
return undefined;
}
return session.shells.get(shellId);
}
removeShell(sessionId: string, shellId: string): boolean {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const shell = session.shells.get(shellId);
if (shell) {
try {
shell.channel.close();
} catch {
// Ignore errors
}
session.shells.delete(shellId);
return true;
}
return false;
}
cleanup(): void {
for (const [sessionId] of this.sessions) {
this.closeSession(sessionId);
}
}
}
// Export singleton instance
export const sessionManager = new SessionManager();