Files
web-antrean/composables/useWebSocket.ts
T
2026-01-30 15:11:17 +07:00

186 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
fallbackPostUrl?: string // Optional: use a specific URL for the POST fallback (e.g., a proxy)
}
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(() => {
let baseUrl = config.url
// Automatically upgrade to secure protocols if caller uses HTTPS
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
if (baseUrl.startsWith('ws://')) {
baseUrl = baseUrl.replace('ws://', 'wss://')
} else if (baseUrl.startsWith('http://')) {
baseUrl = baseUrl.replace('http://', 'https://')
}
}
const url = new URL(baseUrl)
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 {
let postUrl = ''
if (config.fallbackPostUrl) {
// Use the explicitly provided fallback URL (likely a relative proxy path)
postUrl = config.fallbackPostUrl
} else {
// Derive post URL from WebSocket URL (legacy behavior)
let baseUrl = config.url
if (baseUrl.startsWith('ws://')) {
baseUrl = baseUrl.replace('ws://', 'http://')
} else if (baseUrl.startsWith('wss://')) {
baseUrl = baseUrl.replace('wss://', 'https://')
}
const urlObj = new URL(baseUrl)
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),
}
}