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>
85 lines
2.6 KiB
TypeScript
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,
|
|
}
|
|
}
|