wraith/frontend/composables/useSftp.ts
Vantz Stockwell 9e30e5915f fix: SFTP sidebar empty on load — list sent before WebSocket open
The list('/') call fired immediately after connect(), but the
WebSocket was still in CONNECTING state so send() silently dropped
the message. Now buffers the initial list request and sends it
in the onopen callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:56:59 -04:00

85 lines
2.6 KiB
TypeScript

import { ref, type Ref } from 'vue'
import { useAuthStore } from '~/stores/auth.store'
export function useSftp(sessionId: Ref<string | null>) {
const auth = useAuthStore()
let ws: WebSocket | null = null
const entries = ref<any[]>([])
const currentPath = ref('/')
const fileContent = ref<{ path: string; content: string } | null>(null)
const transfers = ref<any[]>([])
let pendingList: string | null = null
function connect() {
const wsUrl = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/api/ws/sftp?token=${auth.token}`
ws = new WebSocket(wsUrl)
ws.onopen = () => {
if (pendingList !== null) {
send({ type: 'list', path: pendingList })
pendingList = null
}
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.type) {
case 'list':
entries.value = msg.entries.sort((a: any, b: any) => {
if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1
return a.name.localeCompare(b.name)
})
currentPath.value = msg.path
break
case 'fileContent':
fileContent.value = { path: msg.path, content: msg.content }
break
case 'saved':
fileContent.value = null
list(currentPath.value)
break
case 'progress':
// Update transfer progress
break
case 'error':
console.error('SFTP error:', msg.message)
break
}
}
return ws
}
function send(msg: any) {
if (ws?.readyState === WebSocket.OPEN && sessionId.value) {
ws.send(JSON.stringify({ ...msg, sessionId: sessionId.value }))
}
}
function list(path: string) {
if (ws && ws.readyState === WebSocket.OPEN) {
send({ type: 'list', path })
} else {
pendingList = path
}
}
function readFile(path: string) { send({ type: 'read', path }) }
function writeFile(path: string, data: string) { send({ type: 'write', path, data }) }
function mkdir(path: string) { send({ type: 'mkdir', path }) }
function rename(oldPath: string, newPath: string) { send({ type: 'rename', oldPath, newPath }) }
function remove(path: string) { send({ type: 'delete', path }) }
function chmod(path: string, mode: string) { send({ type: 'chmod', path, mode }) }
function download(path: string) { send({ type: 'download', path }) }
function disconnect() {
ws?.close()
ws = null
}
return {
entries, currentPath, fileContent, transfers,
connect, disconnect, list, readFile, writeFile, mkdir, rename, remove, chmod, download,
}
}