feat(remote): 完善文件传输功能及WebSocket支持

This commit is contained in:
2026-03-10 01:41:02 +08:00
parent 6d5520dfa5
commit 84e455d9a6
19 changed files with 1263 additions and 5 deletions

190
src/modules/remote/ws.ts Normal file
View File

@@ -0,0 +1,190 @@
type MessageHandler = (data: any) => void
interface PendingRequest {
resolve: (value: any) => void
reject: (reason?: any) => void
timeout: NodeJS.Timeout
}
class RemoteWebSocket {
private ws: WebSocket | null = null
private url: string = ''
private password: string = ''
private pendingRequests: Map<string, PendingRequest> = new Map()
private messageHandlers: Map<string, MessageHandler[]> = new Map()
private reconnectTimer: NodeJS.Timeout | null = null
private isManualClose: boolean = false
connect(serverHost: string, port: number, password?: string): Promise<void> {
this.url = `ws://${serverHost}:${port}/ws`
this.password = password || ''
this.isManualClose = false
return new Promise((resolve, reject) => {
try {
const wsUrl = this.password
? `${this.url}?password=${encodeURIComponent(this.password)}`
: this.url
this.ws = new WebSocket(wsUrl)
this.ws.onopen = () => {
console.log('Remote WebSocket connected')
resolve()
}
this.ws.onclose = () => {
console.log('Remote WebSocket disconnected')
if (!this.isManualClose) {
this.scheduleReconnect()
}
}
this.ws.onerror = (error) => {
console.error('Remote WebSocket error:', error)
reject(error)
}
this.ws.onmessage = (event) => {
try {
if (event.data instanceof Blob || event.data instanceof ArrayBuffer) {
return
}
this.handleMessage(JSON.parse(event.data))
} catch (e) {
console.error('WebSocket message parse error:', e, event.data)
}
}
} catch (error) {
reject(error)
}
})
}
private scheduleReconnect() {
if (this.reconnectTimer) return
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null
if (!this.isManualClose && this.url) {
console.log('Attempting to reconnect Remote WebSocket...')
this.connect(
this.url.replace('ws://', '').split(':')[0],
parseInt(this.url.replace('ws://', '').split(':')[1] || '80'),
this.password
).catch(() => {})
}
}, 3000)
}
disconnect() {
this.isManualClose = true
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
if (this.ws) {
this.ws.close()
this.ws = null
}
}
send(message: any): Promise<any> {
return new Promise((resolve, reject) => {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
reject(new Error('WebSocket not connected'))
return
}
const requestId = message.type + '_' + Date.now() + '_' + Math.random()
message.requestId = requestId
const timeout = setTimeout(() => {
this.pendingRequests.delete(requestId)
reject(new Error('Request timeout'))
}, 30000)
this.pendingRequests.set(requestId, { resolve, reject, timeout })
this.ws.send(JSON.stringify(message))
})
}
on(type: string, handler: MessageHandler) {
if (!this.messageHandlers.has(type)) {
this.messageHandlers.set(type, [])
}
this.messageHandlers.get(type)!.push(handler)
}
off(type: string, handler: MessageHandler) {
const handlers = this.messageHandlers.get(type)
if (handlers) {
const index = handlers.indexOf(handler)
if (index > -1) {
handlers.splice(index, 1)
}
}
}
private handleMessage(data: any) {
const requestId = data.requestId
if (requestId && this.pendingRequests.has(requestId)) {
const { resolve, timeout } = this.pendingRequests.get(requestId)!
clearTimeout(timeout)
this.pendingRequests.delete(requestId)
resolve(data)
return
}
const handlers = this.messageHandlers.get(data.type)
if (handlers) {
handlers.forEach(handler => handler(data))
}
}
isConnected(): boolean {
return this.ws !== null && this.ws.readyState === WebSocket.OPEN
}
}
let instance: RemoteWebSocket | null = null
let lastServerHost: string | null = null
let lastPort: number | null = null
export const getRemoteWebSocket = (
serverHost?: string,
port?: number,
password?: string
): RemoteWebSocket => {
if (!instance) {
instance = new RemoteWebSocket()
}
if (serverHost && port) {
const serverChanged = serverHost !== lastServerHost || port !== lastPort
if (serverChanged) {
instance.disconnect()
lastServerHost = serverHost
lastPort = port
instance.connect(serverHost, port, password).catch(console.error)
} else if (!instance.isConnected()) {
instance.connect(serverHost, port, password).catch(console.error)
}
}
return instance
}
export const createRemoteWebSocket = (
serverHost: string,
port: number,
password?: string
): RemoteWebSocket => {
const ws = new RemoteWebSocket()
ws.connect(serverHost, port, password).catch(console.error)
return ws
}
export { RemoteWebSocket }