feat(remote): 完善文件传输功能及WebSocket支持
This commit is contained in:
190
src/modules/remote/ws.ts
Normal file
190
src/modules/remote/ws.ts
Normal 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 }
|
||||
Reference in New Issue
Block a user