import { defineStore } from "pinia"; import { ref, computed } from "vue"; import { Call } from "@wailsio/runtime"; import { useConnectionStore } from "@/stores/connection.store"; const APP = "github.com/vstockwell/wraith/internal/app.WraithApp"; export interface Session { id: string; connectionId: number; name: string; protocol: "ssh" | "rdp"; active: boolean; } export const useSessionStore = defineStore("session", () => { const sessions = ref([]); const activeSessionId = ref(null); const connecting = ref(false); const lastError = ref(null); const activeSession = computed(() => sessions.value.find((s) => s.id === activeSessionId.value) ?? null, ); const sessionCount = computed(() => sessions.value.length); function activateSession(id: string): void { activeSessionId.value = id; } async function closeSession(id: string): Promise { const idx = sessions.value.findIndex((s) => s.id === id); if (idx === -1) return; const session = sessions.value[idx]; // Disconnect the backend session try { await Call.ByName(`${APP}.DisconnectSession`, session.id); } catch (err) { console.error("Failed to disconnect session:", err); } sessions.value.splice(idx, 1); if (activeSessionId.value === id) { if (sessions.value.length === 0) { activeSessionId.value = null; } else { const nextIdx = Math.min(idx, sessions.value.length - 1); activeSessionId.value = sessions.value[nextIdx].id; } } } /** * Connect to a server by connection ID. * Calls the real Go backend to establish an SSH or RDP session. */ async function connect(connectionId: number): Promise { const connectionStore = useConnectionStore(); const conn = connectionStore.connections.find((c) => c.id === connectionId); if (!conn) return; // Check if there's already an active session for this connection const existing = sessions.value.find((s) => s.connectionId === connectionId); if (existing) { activeSessionId.value = existing.id; return; } connecting.value = true; try { if (conn.protocol === "ssh") { let sessionId: string; try { // Try with stored credentials first sessionId = await Call.ByName( `${APP}.ConnectSSH`, connectionId, 120, // cols (will be resized by xterm.js fit addon) 40, // rows ) as string; } catch (sshErr: any) { const errMsg = typeof sshErr === "string" ? sshErr : sshErr?.message ?? String(sshErr); // If no credentials, prompt for username/password if (errMsg.includes("NO_CREDENTIALS") || errMsg.includes("unable to authenticate")) { const username = prompt(`Username for ${conn.hostname}:`, "root"); if (!username) throw new Error("Connection cancelled"); const password = prompt(`Password for ${username}@${conn.hostname}:`); if (password === null) throw new Error("Connection cancelled"); sessionId = await Call.ByName( `${APP}.ConnectSSHWithPassword`, connectionId, username, password, 120, 40, ) as string; } else { throw sshErr; } } sessions.value.push({ id: sessionId, connectionId, name: conn.name, protocol: "ssh", active: true, }); activeSessionId.value = sessionId; } else if (conn.protocol === "rdp") { // Call Go backend — resolves credentials, builds RDPConfig, returns sessionID const sessionId = await Call.ByName( `${APP}.ConnectRDP`, connectionId, 1920, // initial width — resized by the RDP view on mount 1080, // initial height ) as string; sessions.value.push({ id: sessionId, connectionId, name: conn.name, protocol: "rdp", active: true, }); activeSessionId.value = sessionId; } } catch (err: any) { const msg = typeof err === "string" ? err : err?.message ?? String(err); console.error("Connection failed:", msg); lastError.value = msg; // Show error as native alert so it's visible without DevTools alert(`Connection failed: ${msg}`); } finally { connecting.value = false; } } return { sessions, activeSessionId, activeSession, sessionCount, connecting, lastError, activateSession, closeSession, connect, }; });