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

This commit is contained in:
2026-01-10 23:54:31 +00:00
parent 63c578b0ae
commit 53d0c89d17
8 changed files with 2220 additions and 172 deletions

View File

@@ -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,
}
})
};
});

View File

@@ -12,6 +12,13 @@ export default defineConfig({
},
server: {
port: 5173,
host: true, // Listen on all addresses
allowedHosts: [
".trycloudflare.com", // Cloudflare Quick Tunnels
".turbo.local", // Custom tunnel domains
".devtunnels.ms", // VS Code Dev Tunnels
"localhost",
],
proxy: {
"/api": {
target: "http://localhost:3000",
@@ -19,8 +26,9 @@ export default defineConfig({
// Don't rewrite - backend expects /api prefix
},
"/ws": {
target: "ws://localhost:3000",
target: "http://localhost:3000",
ws: true,
changeOrigin: true,
},
},
},