wraith/frontend/composables/useSftp.ts

141 lines
5.1 KiB
TypeScript

import { ref, watch, 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[]>([])
const activeDownloads = new Map<string, { path: string; chunks: string[]; total: number }>()
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 = () => {
console.log('[SFTP] WS open, sessionId=', sessionId.value, 'pendingList=', pendingList)
if (pendingList !== null) {
// Send directly — don't rely on send() guard since we know WS is open
const path = pendingList
pendingList = null
if (sessionId.value) {
ws!.send(JSON.stringify({ type: 'list', path, sessionId: sessionId.value }))
} else {
console.warn('[SFTP] No sessionId available yet, cannot list')
pendingList = path // put it back
}
}
}
ws.onmessage = (event) => {
console.log('[SFTP] WS message received, raw length:', event.data?.length, 'type:', typeof event.data)
const msg = JSON.parse(event.data)
console.log('[SFTP] Parsed message type:', msg.type, 'entries:', msg.entries?.length)
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
console.log('[SFTP] entries.value set, count:', entries.value.length)
break
case 'fileContent':
fileContent.value = { path: msg.path, content: msg.content }
break
case 'saved':
fileContent.value = null
list(currentPath.value)
break
case 'uploaded':
list(currentPath.value)
break
case 'downloadStart':
activeDownloads.set(msg.transferId, { path: msg.path, chunks: [], total: msg.total })
break
case 'downloadChunk':
const dl = activeDownloads.get(msg.transferId)
if (dl) dl.chunks.push(msg.data)
break
case 'downloadComplete': {
const transfer = activeDownloads.get(msg.transferId)
if (transfer) {
const binary = atob(transfer.chunks.join(''))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
const blob = new Blob([bytes])
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = transfer.path.split('/').pop() || 'download'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
activeDownloads.delete(msg.transferId)
}
break
}
case 'error':
console.error('SFTP error:', msg.message)
break
}
}
ws.onerror = (event) => {
console.error('[SFTP] WS error event:', event)
}
ws.onclose = (event) => {
console.log('[SFTP] WS closed, code:', event.code, 'reason:', event.reason, 'wasClean:', event.wasClean)
}
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 upload(path: string, dataBase64: string) { send({ type: 'upload', path, data: dataBase64 }) }
// If sessionId arrives after WS is already open, send any pending list
watch(sessionId, (newId) => {
if (newId && pendingList !== null && ws?.readyState === WebSocket.OPEN) {
const path = pendingList
pendingList = null
ws!.send(JSON.stringify({ type: 'list', path, sessionId: newId }))
}
})
function disconnect() {
ws?.close()
ws = null
}
return {
entries, currentPath, fileContent, transfers,
connect, disconnect, list, readFile, writeFile, mkdir, rename, remove, chmod, download, upload,
}
}