wraith/frontend/src/stores/session.store.ts
Vantz Stockwell 163af456b4
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
fix: SSH password prompt on auth failure, version from Go backend, visible errors
- ConnectSSH returns NO_CREDENTIALS error when no credential is stored
- Frontend catches auth failures and prompts for username/password
- ConnectSSHWithPassword method for ad-hoc password auth
- Version loaded from Go backend (build-time -ldflags) in settings + unlock screen
- Connection errors shown as alert() instead of silent console.error
- Added UpdateService.CurrentVersion() and WraithApp.GetVersion()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:38:08 -04:00

158 lines
4.6 KiB
TypeScript

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<Session[]>([]);
const activeSessionId = ref<string | null>(null);
const connecting = ref(false);
const lastError = ref<string | null>(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<void> {
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<void> {
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,
};
});