import { ref, watch, type Ref } from 'vue' import { useAuthStore } from '~/stores/auth.store' export function useSftp(sessionId: Ref) { const auth = useAuthStore() let ws: WebSocket | null = null const entries = ref([]) const currentPath = ref('/') const fileContent = ref<{ path: string; content: string } | null>(null) const transfers = ref([]) const activeDownloads = new Map() 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, } }