first commit

This commit is contained in:
2026-01-10 04:57:43 +00:00
parent 16a76a2cd6
commit 232968de1e
131 changed files with 43262 additions and 0 deletions

View File

@@ -0,0 +1,341 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useAuthStore } from './auth'
import { useToast } from 'vue-toastification'
const toast = useToast()
export const useWebSocketStore = defineStore('websocket', () => {
// State
const ws = ref(null)
const isConnected = ref(false)
const isConnecting = ref(false)
const reconnectAttempts = ref(0)
const maxReconnectAttempts = ref(5)
const reconnectDelay = ref(1000)
const heartbeatInterval = ref(null)
const reconnectTimeout = ref(null)
const messageQueue = ref([])
const listeners = ref(new Map())
// Computed
const connectionStatus = computed(() => {
if (isConnected.value) return 'connected'
if (isConnecting.value) return 'connecting'
return 'disconnected'
})
const canReconnect = computed(() => {
return reconnectAttempts.value < maxReconnectAttempts.value
})
// Helper functions
const getWebSocketUrl = () => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
// In development, use the proxy
if (import.meta.env.DEV) {
return `ws://localhost:3000/ws`
}
return `${protocol}//${host}/ws`
}
const clearHeartbeat = () => {
if (heartbeatInterval.value) {
clearInterval(heartbeatInterval.value)
heartbeatInterval.value = null
}
}
const clearReconnectTimeout = () => {
if (reconnectTimeout.value) {
clearTimeout(reconnectTimeout.value)
reconnectTimeout.value = null
}
}
const startHeartbeat = () => {
clearHeartbeat()
// Send ping every 30 seconds
heartbeatInterval.value = setInterval(() => {
if (isConnected.value && ws.value?.readyState === WebSocket.OPEN) {
send({ type: 'ping' })
}
}, 30000)
}
// Actions
const connect = () => {
if (ws.value?.readyState === WebSocket.OPEN || isConnecting.value) {
console.log('WebSocket already connected or connecting')
return
}
isConnecting.value = true
clearReconnectTimeout()
try {
const wsUrl = getWebSocketUrl()
console.log('Connecting to WebSocket:', wsUrl)
ws.value = new WebSocket(wsUrl)
ws.value.onopen = () => {
console.log('WebSocket connected')
isConnected.value = true
isConnecting.value = false
reconnectAttempts.value = 0
startHeartbeat()
// Send queued messages
while (messageQueue.value.length > 0) {
const message = messageQueue.value.shift()
send(message)
}
// Emit connected event
emit('connected', { timestamp: Date.now() })
}
ws.value.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
console.log('WebSocket message received:', data)
handleMessage(data)
} catch (error) {
console.error('Failed to parse WebSocket message:', error)
}
}
ws.value.onerror = (error) => {
console.error('WebSocket error:', error)
isConnecting.value = false
}
ws.value.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason)
isConnected.value = false
isConnecting.value = false
clearHeartbeat()
// Emit disconnected event
emit('disconnected', {
code: event.code,
reason: event.reason,
timestamp: Date.now()
})
// Attempt to reconnect
if (!event.wasClean && canReconnect.value) {
scheduleReconnect()
}
}
} catch (error) {
console.error('Failed to create WebSocket connection:', error)
isConnecting.value = false
}
}
const disconnect = () => {
clearHeartbeat()
clearReconnectTimeout()
reconnectAttempts.value = maxReconnectAttempts.value // Prevent auto-reconnect
if (ws.value) {
ws.value.close(1000, 'Client disconnect')
ws.value = null
}
isConnected.value = false
isConnecting.value = false
}
const scheduleReconnect = () => {
if (!canReconnect.value) {
console.log('Max reconnect attempts reached')
toast.error('Lost connection to server. Please refresh the page.')
return
}
reconnectAttempts.value++
const delay = reconnectDelay.value * Math.pow(2, reconnectAttempts.value - 1)
console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.value}/${maxReconnectAttempts.value})`)
clearReconnectTimeout()
reconnectTimeout.value = setTimeout(() => {
connect()
}, delay)
}
const send = (message) => {
if (!ws.value || ws.value.readyState !== WebSocket.OPEN) {
console.warn('WebSocket not connected, queueing message:', message)
messageQueue.value.push(message)
return false
}
try {
const payload = typeof message === 'string' ? message : JSON.stringify(message)
ws.value.send(payload)
return true
} catch (error) {
console.error('Failed to send WebSocket message:', error)
return false
}
}
const handleMessage = (data) => {
const { type, data: payload, timestamp } = data
switch (type) {
case 'connected':
console.log('Server confirmed connection:', payload)
break
case 'pong':
// Heartbeat response
break
case 'notification':
if (payload?.message) {
toast.info(payload.message)
}
break
case 'balance_update':
// Update user balance
const authStore = useAuthStore()
if (payload?.balance !== undefined) {
authStore.updateBalance(payload.balance)
}
break
case 'item_sold':
toast.success(`Your item "${payload?.itemName || 'item'}" has been sold!`)
break
case 'item_purchased':
toast.success(`Successfully purchased "${payload?.itemName || 'item'}"!`)
break
case 'trade_status':
if (payload?.status === 'completed') {
toast.success('Trade completed successfully!')
} else if (payload?.status === 'failed') {
toast.error(`Trade failed: ${payload?.reason || 'Unknown error'}`)
}
break
case 'price_update':
case 'listing_update':
case 'market_update':
// These will be handled by listeners
break
case 'announcement':
if (payload?.message) {
toast.warning(payload.message, { timeout: 10000 })
}
break
case 'error':
console.error('Server error:', payload)
if (payload?.message) {
toast.error(payload.message)
}
break
default:
console.log('Unhandled message type:', type)
}
// Emit to listeners
emit(type, payload)
}
const on = (event, callback) => {
if (!listeners.value.has(event)) {
listeners.value.set(event, [])
}
listeners.value.get(event).push(callback)
// Return unsubscribe function
return () => off(event, callback)
}
const off = (event, callback) => {
if (!listeners.value.has(event)) return
const callbacks = listeners.value.get(event)
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
if (callbacks.length === 0) {
listeners.value.delete(event)
}
}
const emit = (event, data) => {
if (!listeners.value.has(event)) return
const callbacks = listeners.value.get(event)
callbacks.forEach(callback => {
try {
callback(data)
} catch (error) {
console.error(`Error in event listener for "${event}":`, error)
}
})
}
const once = (event, callback) => {
const wrappedCallback = (data) => {
callback(data)
off(event, wrappedCallback)
}
return on(event, wrappedCallback)
}
const clearListeners = () => {
listeners.value.clear()
}
// Ping the server
const ping = () => {
send({ type: 'ping' })
}
return {
// State
ws,
isConnected,
isConnecting,
reconnectAttempts,
maxReconnectAttempts,
messageQueue,
// Computed
connectionStatus,
canReconnect,
// Actions
connect,
disconnect,
send,
on,
off,
once,
emit,
clearListeners,
ping,
}
})