Clean up tunnel infrastructure and migrate to Gitea
Some checks failed
Deploy to Production Server / Deploy to 178.63.127.19 (push) Has been cancelled
Some checks failed
Deploy to Production Server / Deploy to 178.63.127.19 (push) Has been cancelled
This commit is contained in:
@@ -1,318 +1,322 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useAuthStore } from './auth'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import { useAuthStore } from "./auth";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast()
|
||||
const toast = useToast();
|
||||
|
||||
export const useWebSocketStore = defineStore('websocket', () => {
|
||||
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())
|
||||
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'
|
||||
})
|
||||
if (isConnected.value) return "connected";
|
||||
if (isConnecting.value) return "connecting";
|
||||
return "disconnected";
|
||||
});
|
||||
|
||||
const canReconnect = computed(() => {
|
||||
return reconnectAttempts.value < maxReconnectAttempts.value
|
||||
})
|
||||
return reconnectAttempts.value < maxReconnectAttempts.value;
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
const getWebSocketUrl = () => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const host = window.location.host
|
||||
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`
|
||||
}
|
||||
// Always use the current host (works with VS Code tunnels and proxies)
|
||||
return `${protocol}//${host}/ws`;
|
||||
};
|
||||
|
||||
const clearHeartbeat = () => {
|
||||
if (heartbeatInterval.value) {
|
||||
clearInterval(heartbeatInterval.value)
|
||||
heartbeatInterval.value = null
|
||||
clearInterval(heartbeatInterval.value);
|
||||
heartbeatInterval.value = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const clearReconnectTimeout = () => {
|
||||
if (reconnectTimeout.value) {
|
||||
clearTimeout(reconnectTimeout.value)
|
||||
reconnectTimeout.value = null
|
||||
clearTimeout(reconnectTimeout.value);
|
||||
reconnectTimeout.value = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const startHeartbeat = () => {
|
||||
clearHeartbeat()
|
||||
clearHeartbeat();
|
||||
|
||||
// Send ping every 30 seconds
|
||||
heartbeatInterval.value = setInterval(() => {
|
||||
if (isConnected.value && ws.value?.readyState === WebSocket.OPEN) {
|
||||
send({ type: 'ping' })
|
||||
send({ type: "ping" });
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
}, 30000);
|
||||
};
|
||||
|
||||
// Actions
|
||||
const connect = () => {
|
||||
if (ws.value?.readyState === WebSocket.OPEN || isConnecting.value) {
|
||||
console.log('WebSocket already connected or connecting')
|
||||
return
|
||||
console.log("WebSocket already connected or connecting");
|
||||
return;
|
||||
}
|
||||
|
||||
isConnecting.value = true
|
||||
clearReconnectTimeout()
|
||||
isConnecting.value = true;
|
||||
clearReconnectTimeout();
|
||||
|
||||
try {
|
||||
const wsUrl = getWebSocketUrl()
|
||||
console.log('Connecting to WebSocket:', wsUrl)
|
||||
const wsUrl = getWebSocketUrl();
|
||||
console.log("Connecting to WebSocket:", wsUrl);
|
||||
|
||||
ws.value = new WebSocket(wsUrl)
|
||||
ws.value = new WebSocket(wsUrl);
|
||||
|
||||
ws.value.onopen = () => {
|
||||
console.log('WebSocket connected')
|
||||
isConnected.value = true
|
||||
isConnecting.value = false
|
||||
reconnectAttempts.value = 0
|
||||
console.log("WebSocket connected");
|
||||
isConnected.value = true;
|
||||
isConnecting.value = false;
|
||||
reconnectAttempts.value = 0;
|
||||
|
||||
startHeartbeat()
|
||||
startHeartbeat();
|
||||
|
||||
// Send queued messages
|
||||
while (messageQueue.value.length > 0) {
|
||||
const message = messageQueue.value.shift()
|
||||
send(message)
|
||||
const message = messageQueue.value.shift();
|
||||
send(message);
|
||||
}
|
||||
|
||||
// Emit connected event
|
||||
emit('connected', { timestamp: Date.now() })
|
||||
}
|
||||
emit("connected", { timestamp: Date.now() });
|
||||
};
|
||||
|
||||
ws.value.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
console.log('WebSocket message received:', data)
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("WebSocket message received:", data);
|
||||
|
||||
handleMessage(data)
|
||||
handleMessage(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error)
|
||||
console.error("Failed to parse WebSocket message:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.value.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
isConnecting.value = false
|
||||
}
|
||||
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()
|
||||
console.log("WebSocket closed:", event.code, event.reason);
|
||||
isConnected.value = false;
|
||||
isConnecting.value = false;
|
||||
clearHeartbeat();
|
||||
|
||||
// Emit disconnected event
|
||||
emit('disconnected', {
|
||||
emit("disconnected", {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Attempt to reconnect
|
||||
if (!event.wasClean && canReconnect.value) {
|
||||
scheduleReconnect()
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to create WebSocket connection:', error)
|
||||
isConnecting.value = false
|
||||
console.error("Failed to create WebSocket connection:", error);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const disconnect = () => {
|
||||
clearHeartbeat()
|
||||
clearReconnectTimeout()
|
||||
reconnectAttempts.value = maxReconnectAttempts.value // Prevent auto-reconnect
|
||||
clearHeartbeat();
|
||||
clearReconnectTimeout();
|
||||
reconnectAttempts.value = maxReconnectAttempts.value; // Prevent auto-reconnect
|
||||
|
||||
if (ws.value) {
|
||||
ws.value.close(1000, 'Client disconnect')
|
||||
ws.value = null
|
||||
ws.value.close(1000, "Client disconnect");
|
||||
ws.value = null;
|
||||
}
|
||||
|
||||
isConnected.value = false
|
||||
isConnecting.value = false
|
||||
}
|
||||
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
|
||||
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)
|
||||
reconnectAttempts.value++;
|
||||
const delay =
|
||||
reconnectDelay.value * Math.pow(2, reconnectAttempts.value - 1);
|
||||
|
||||
console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.value}/${maxReconnectAttempts.value})`)
|
||||
console.log(
|
||||
`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.value}/${maxReconnectAttempts.value})`
|
||||
);
|
||||
|
||||
clearReconnectTimeout()
|
||||
clearReconnectTimeout();
|
||||
reconnectTimeout.value = setTimeout(() => {
|
||||
connect()
|
||||
}, delay)
|
||||
}
|
||||
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
|
||||
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
|
||||
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
|
||||
console.error("Failed to send WebSocket message:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMessage = (data) => {
|
||||
const { type, data: payload, timestamp } = data
|
||||
const { type, data: payload, timestamp } = data;
|
||||
|
||||
switch (type) {
|
||||
case 'connected':
|
||||
console.log('Server confirmed connection:', payload)
|
||||
break
|
||||
case "connected":
|
||||
console.log("Server confirmed connection:", payload);
|
||||
break;
|
||||
|
||||
case 'pong':
|
||||
case "pong":
|
||||
// Heartbeat response
|
||||
break
|
||||
break;
|
||||
|
||||
case 'notification':
|
||||
case "notification":
|
||||
if (payload?.message) {
|
||||
toast.info(payload.message)
|
||||
toast.info(payload.message);
|
||||
}
|
||||
break
|
||||
break;
|
||||
|
||||
case 'balance_update':
|
||||
case "balance_update":
|
||||
// Update user balance
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
if (payload?.balance !== undefined) {
|
||||
authStore.updateBalance(payload.balance)
|
||||
authStore.updateBalance(payload.balance);
|
||||
}
|
||||
break
|
||||
break;
|
||||
|
||||
case 'item_sold':
|
||||
toast.success(`Your item "${payload?.itemName || 'item'}" has been sold!`)
|
||||
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 "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'}`)
|
||||
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
|
||||
break;
|
||||
|
||||
case 'price_update':
|
||||
case 'listing_update':
|
||||
case 'market_update':
|
||||
case "price_update":
|
||||
case "listing_update":
|
||||
case "market_update":
|
||||
// These will be handled by listeners
|
||||
break
|
||||
break;
|
||||
|
||||
case 'announcement':
|
||||
case "announcement":
|
||||
if (payload?.message) {
|
||||
toast.warning(payload.message, { timeout: 10000 })
|
||||
toast.warning(payload.message, { timeout: 10000 });
|
||||
}
|
||||
break
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error('Server error:', payload)
|
||||
case "error":
|
||||
console.error("Server error:", payload);
|
||||
if (payload?.message) {
|
||||
toast.error(payload.message)
|
||||
toast.error(payload.message);
|
||||
}
|
||||
break
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Unhandled message type:', type)
|
||||
console.log("Unhandled message type:", type);
|
||||
}
|
||||
|
||||
// Emit to listeners
|
||||
emit(type, payload)
|
||||
}
|
||||
emit(type, payload);
|
||||
};
|
||||
|
||||
const on = (event, callback) => {
|
||||
if (!listeners.value.has(event)) {
|
||||
listeners.value.set(event, [])
|
||||
listeners.value.set(event, []);
|
||||
}
|
||||
listeners.value.get(event).push(callback)
|
||||
listeners.value.get(event).push(callback);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => off(event, callback)
|
||||
}
|
||||
return () => off(event, callback);
|
||||
};
|
||||
|
||||
const off = (event, callback) => {
|
||||
if (!listeners.value.has(event)) return
|
||||
if (!listeners.value.has(event)) return;
|
||||
|
||||
const callbacks = listeners.value.get(event)
|
||||
const index = callbacks.indexOf(callback)
|
||||
const callbacks = listeners.value.get(event);
|
||||
const index = callbacks.indexOf(callback);
|
||||
|
||||
if (index > -1) {
|
||||
callbacks.splice(index, 1)
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
|
||||
if (callbacks.length === 0) {
|
||||
listeners.value.delete(event)
|
||||
listeners.value.delete(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const emit = (event, data) => {
|
||||
if (!listeners.value.has(event)) return
|
||||
if (!listeners.value.has(event)) return;
|
||||
|
||||
const callbacks = listeners.value.get(event)
|
||||
callbacks.forEach(callback => {
|
||||
const callbacks = listeners.value.get(event);
|
||||
callbacks.forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in event listener for "${event}":`, 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)
|
||||
}
|
||||
callback(data);
|
||||
off(event, wrappedCallback);
|
||||
};
|
||||
return on(event, wrappedCallback);
|
||||
};
|
||||
|
||||
const clearListeners = () => {
|
||||
listeners.value.clear()
|
||||
}
|
||||
listeners.value.clear();
|
||||
};
|
||||
|
||||
// Ping the server
|
||||
const ping = () => {
|
||||
send({ type: 'ping' })
|
||||
}
|
||||
send({ type: "ping" });
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
@@ -337,5 +341,5 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
emit,
|
||||
clearListeners,
|
||||
ping,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user