168 lines
4.7 KiB
TypeScript
168 lines
4.7 KiB
TypeScript
// composables/useWebSocket.ts
|
||
import { ref, computed, onUnmounted } from 'vue'
|
||
|
||
export interface WebSocketConfig {
|
||
url: string
|
||
clientId: string
|
||
onMessage?: (data: any) => void
|
||
onOpen?: () => void
|
||
onClose?: () => void
|
||
onError?: (error: Event) => void
|
||
reconnectInterval?: number
|
||
maxReconnectAttempts?: number
|
||
}
|
||
|
||
export interface WebSocketMessage {
|
||
to_client: string
|
||
data: any
|
||
}
|
||
|
||
export const useWebSocket = (config: WebSocketConfig) => {
|
||
const ws = ref<WebSocket | null>(null)
|
||
const isConnected = ref(false)
|
||
const reconnectAttempts = ref(0)
|
||
const reconnectTimer = ref<NodeJS.Timeout | null>(null)
|
||
|
||
const wsUrl = computed(() => {
|
||
const url = new URL(config.url)
|
||
url.searchParams.set('client_id', config.clientId)
|
||
return url.toString()
|
||
})
|
||
|
||
const connect = () => {
|
||
try {
|
||
// Close existing connection if any
|
||
if (ws.value && ws.value.readyState !== WebSocket.CLOSED) {
|
||
ws.value.close()
|
||
}
|
||
|
||
ws.value = new WebSocket(wsUrl.value)
|
||
|
||
ws.value.onopen = () => {
|
||
console.log('WebSocket connected:', config.clientId)
|
||
isConnected.value = true
|
||
reconnectAttempts.value = 0
|
||
config.onOpen?.()
|
||
}
|
||
|
||
ws.value.onmessage = (event) => {
|
||
try {
|
||
const data = JSON.parse(event.data)
|
||
config.onMessage?.(data)
|
||
} catch (error) {
|
||
console.error('Error parsing WebSocket message:', error)
|
||
}
|
||
}
|
||
|
||
ws.value.onclose = () => {
|
||
console.log('WebSocket closed:', config.clientId)
|
||
isConnected.value = false
|
||
config.onClose?.()
|
||
|
||
// Attempt to reconnect
|
||
if (reconnectAttempts.value < (config.maxReconnectAttempts || 5)) {
|
||
reconnectAttempts.value++
|
||
const interval = config.reconnectInterval || 3000
|
||
reconnectTimer.value = setTimeout(() => {
|
||
console.log(`Reconnecting... Attempt ${reconnectAttempts.value}`)
|
||
connect()
|
||
}, interval)
|
||
} else {
|
||
console.error('Max reconnection attempts reached')
|
||
}
|
||
}
|
||
|
||
ws.value.onerror = (error) => {
|
||
console.error('WebSocket error:', error)
|
||
config.onError?.(error)
|
||
}
|
||
} catch (error) {
|
||
console.error('Error creating WebSocket:', error)
|
||
config.onError?.(error as Event)
|
||
}
|
||
}
|
||
|
||
const disconnect = () => {
|
||
if (reconnectTimer.value) {
|
||
clearTimeout(reconnectTimer.value)
|
||
reconnectTimer.value = null
|
||
}
|
||
if (ws.value) {
|
||
ws.value.close()
|
||
ws.value = null
|
||
}
|
||
isConnected.value = false
|
||
}
|
||
|
||
const sendMessage = (message: any) => {
|
||
if (ws.value && ws.value.readyState === WebSocket.OPEN) {
|
||
ws.value.send(JSON.stringify(message))
|
||
return true
|
||
}
|
||
console.warn('WebSocket is not connected')
|
||
return false
|
||
}
|
||
|
||
// Send data via POST to WebSocket server
|
||
const sendViaPost = async (message: WebSocketMessage) => {
|
||
try {
|
||
// Extract base URL from WebSocket URL (convert ws:// to http:// or wss:// to https://)
|
||
let baseUrl = config.url
|
||
if (baseUrl.startsWith('ws://')) {
|
||
baseUrl = baseUrl.replace('ws://', 'http://')
|
||
} else if (baseUrl.startsWith('wss://')) {
|
||
baseUrl = baseUrl.replace('wss://', 'https://')
|
||
}
|
||
|
||
// Remove query parameters and ensure we have the correct POST endpoint
|
||
const urlObj = new URL(baseUrl)
|
||
const postUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`
|
||
|
||
console.log('📡 POST URL:', postUrl)
|
||
console.log('📦 POST Body:', JSON.stringify(message, null, 2))
|
||
|
||
const response = await fetch(postUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(message),
|
||
})
|
||
|
||
console.log('📥 POST Response status:', response.status, response.statusText)
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text().catch(() => 'Unknown error')
|
||
console.error('❌ POST Error response:', errorText)
|
||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
|
||
}
|
||
|
||
const result = await response.json().catch(() => {
|
||
console.log('ℹ️ Response is not JSON, assuming success')
|
||
return { success: true }
|
||
})
|
||
|
||
console.log('✅ POST Response data:', result)
|
||
return result
|
||
} catch (error) {
|
||
console.error('❌ Error sending message via POST:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
onUnmounted(() => {
|
||
disconnect()
|
||
})
|
||
|
||
return {
|
||
ws: computed(() => ws.value),
|
||
isConnected: computed(() => isConnected.value),
|
||
connect,
|
||
disconnect,
|
||
sendMessage,
|
||
sendViaPost,
|
||
reconnectAttempts: computed(() => reconnectAttempts.value),
|
||
}
|
||
}
|
||
|