| | |
| | |
| |
|
| |
|
| | class CryptoWebSocketClient {
|
| | constructor(url = null) {
|
| | this.url = url || `ws://${window.location.host}/ws`;
|
| | this.ws = null;
|
| | this.sessionId = null;
|
| | this.isConnected = false;
|
| | this.reconnectAttempts = 0;
|
| | this.maxReconnectAttempts = 5;
|
| | this.reconnectDelay = 3000;
|
| | this.messageHandlers = {};
|
| | this.connectionCallbacks = [];
|
| |
|
| | this.connect();
|
| | }
|
| |
|
| | connect() {
|
| | try {
|
| | console.log('🔌 اتصال به WebSocket:', this.url);
|
| | this.ws = new WebSocket(this.url);
|
| |
|
| | this.ws.onopen = this.onOpen.bind(this);
|
| | this.ws.onmessage = this.onMessage.bind(this);
|
| | this.ws.onerror = this.onError.bind(this);
|
| | this.ws.onclose = this.onClose.bind(this);
|
| |
|
| | } catch (error) {
|
| | console.error('❌ خطا در اتصال WebSocket:', error);
|
| | this.scheduleReconnect();
|
| | }
|
| | }
|
| |
|
| | onOpen(event) {
|
| | console.log('✅ WebSocket متصل شد');
|
| | this.isConnected = true;
|
| | this.reconnectAttempts = 0;
|
| |
|
| |
|
| | this.connectionCallbacks.forEach(cb => cb(true));
|
| |
|
| |
|
| | this.updateConnectionStatus(true);
|
| | }
|
| |
|
| | onMessage(event) {
|
| | try {
|
| | const message = JSON.parse(event.data);
|
| | const type = message.type;
|
| |
|
| |
|
| | if (type === 'welcome') {
|
| | this.sessionId = message.session_id;
|
| | console.log('📝 Session ID:', this.sessionId);
|
| | }
|
| |
|
| | else if (type === 'stats_update') {
|
| | this.handleStatsUpdate(message.data);
|
| | }
|
| |
|
| | else if (type === 'provider_stats') {
|
| | this.handleProviderStats(message.data);
|
| | }
|
| |
|
| | else if (type === 'market_update') {
|
| | this.handleMarketUpdate(message.data);
|
| | }
|
| |
|
| | else if (type === 'price_update') {
|
| | this.handlePriceUpdate(message.data);
|
| | }
|
| |
|
| | else if (type === 'alert') {
|
| | this.handleAlert(message.data);
|
| | }
|
| |
|
| | else if (type === 'heartbeat') {
|
| |
|
| | this.send({ type: 'pong' });
|
| | }
|
| |
|
| |
|
| | if (this.messageHandlers[type]) {
|
| | this.messageHandlers[type](message);
|
| | }
|
| |
|
| | } catch (error) {
|
| | console.error('❌ خطا در پردازش پیام:', error);
|
| | }
|
| | }
|
| |
|
| | onError(error) {
|
| | console.error('❌ خطای WebSocket:', error);
|
| | this.isConnected = false;
|
| | this.updateConnectionStatus(false);
|
| | }
|
| |
|
| | onClose(event) {
|
| | console.log('🔌 WebSocket قطع شد');
|
| | this.isConnected = false;
|
| | this.sessionId = null;
|
| |
|
| | this.connectionCallbacks.forEach(cb => cb(false));
|
| | this.updateConnectionStatus(false);
|
| |
|
| |
|
| | this.scheduleReconnect();
|
| | }
|
| |
|
| | scheduleReconnect() {
|
| | if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
| | this.reconnectAttempts++;
|
| | console.log(`🔄 تلاش مجدد برای اتصال (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
| |
|
| | setTimeout(() => {
|
| | this.connect();
|
| | }, this.reconnectDelay);
|
| | } else {
|
| | console.error('❌ تعداد تلاشهای اتصال به پایان رسید');
|
| | this.showReconnectButton();
|
| | }
|
| | }
|
| |
|
| | send(data) {
|
| | if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
|
| | this.ws.send(JSON.stringify(data));
|
| | } else {
|
| | console.warn('⚠️ WebSocket متصل نیست');
|
| | }
|
| | }
|
| |
|
| | subscribe(group) {
|
| | this.send({
|
| | type: 'subscribe',
|
| | group: group
|
| | });
|
| | }
|
| |
|
| | unsubscribe(group) {
|
| | this.send({
|
| | type: 'unsubscribe',
|
| | group: group
|
| | });
|
| | }
|
| |
|
| | requestStats() {
|
| | this.send({
|
| | type: 'get_stats'
|
| | });
|
| | }
|
| |
|
| | on(type, handler) {
|
| | this.messageHandlers[type] = handler;
|
| | }
|
| |
|
| | onConnection(callback) {
|
| | this.connectionCallbacks.push(callback);
|
| | }
|
| |
|
| |
|
| |
|
| | handleStatsUpdate(data) {
|
| |
|
| | const activeConnections = data.active_connections || 0;
|
| | const totalSessions = data.total_sessions || 0;
|
| |
|
| |
|
| | this.updateOnlineUsers(activeConnections, totalSessions);
|
| |
|
| |
|
| | if (data.client_types) {
|
| | this.updateClientTypes(data.client_types);
|
| | }
|
| | }
|
| |
|
| | handleProviderStats(data) {
|
| |
|
| | const summary = data.summary || {};
|
| |
|
| |
|
| | if (window.updateProviderStats) {
|
| | window.updateProviderStats(summary);
|
| | }
|
| | }
|
| |
|
| | handleMarketUpdate(data) {
|
| | if (window.updateMarketData) {
|
| | window.updateMarketData(data);
|
| | }
|
| | }
|
| |
|
| | handlePriceUpdate(data) {
|
| | if (window.updatePrice) {
|
| | window.updatePrice(data.symbol, data.price, data.change_24h);
|
| | }
|
| | }
|
| |
|
| | handleAlert(data) {
|
| | this.showAlert(data.message, data.severity);
|
| | }
|
| |
|
| |
|
| |
|
| | updateConnectionStatus(connected) {
|
| | const statusEl = document.getElementById('ws-connection-status');
|
| | const statusDot = document.getElementById('ws-status-dot');
|
| | const statusText = document.getElementById('ws-status-text');
|
| |
|
| | if (statusEl && statusDot && statusText) {
|
| | if (connected) {
|
| | statusDot.className = 'status-dot status-dot-online';
|
| | statusText.textContent = 'متصل';
|
| | statusEl.classList.add('connected');
|
| | statusEl.classList.remove('disconnected');
|
| | } else {
|
| | statusDot.className = 'status-dot status-dot-offline';
|
| | statusText.textContent = 'قطع شده';
|
| | statusEl.classList.add('disconnected');
|
| | statusEl.classList.remove('connected');
|
| | }
|
| | }
|
| | }
|
| |
|
| | updateOnlineUsers(active, total) {
|
| | const activeEl = document.getElementById('active-users-count');
|
| | const totalEl = document.getElementById('total-sessions-count');
|
| | const badgeEl = document.getElementById('online-users-badge');
|
| |
|
| | if (activeEl) {
|
| | activeEl.textContent = active;
|
| |
|
| | activeEl.classList.add('count-updated');
|
| | setTimeout(() => activeEl.classList.remove('count-updated'), 500);
|
| | }
|
| |
|
| | if (totalEl) {
|
| | totalEl.textContent = total;
|
| | }
|
| |
|
| | if (badgeEl) {
|
| | badgeEl.textContent = active;
|
| | badgeEl.classList.add('pulse');
|
| | setTimeout(() => badgeEl.classList.remove('pulse'), 1000);
|
| | }
|
| | }
|
| |
|
| | updateClientTypes(types) {
|
| | const listEl = document.getElementById('client-types-list');
|
| | if (listEl && types) {
|
| | const html = Object.entries(types).map(([type, count]) =>
|
| | `<div class="client-type-item">
|
| | <span class="client-type-name">${type}</span>
|
| | <span class="client-type-count">${count}</span>
|
| | </div>`
|
| | ).join('');
|
| | listEl.innerHTML = html;
|
| | }
|
| | }
|
| |
|
| | showAlert(message, severity = 'info') {
|
| |
|
| | const alert = document.createElement('div');
|
| | alert.className = `alert alert-${severity} alert-dismissible fade show`;
|
| | alert.innerHTML = `
|
| | <strong>${severity === 'error' ? '❌' : severity === 'warning' ? '⚠️' : 'ℹ️'}</strong>
|
| | ${message}
|
| | <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
| | `;
|
| |
|
| | const container = document.getElementById('alerts-container') || document.body;
|
| | container.appendChild(alert);
|
| |
|
| |
|
| | setTimeout(() => {
|
| | alert.classList.remove('show');
|
| | setTimeout(() => alert.remove(), 300);
|
| | }, 5000);
|
| | }
|
| |
|
| | showReconnectButton() {
|
| | const button = document.createElement('button');
|
| | button.className = 'btn btn-warning reconnect-btn';
|
| | button.innerHTML = '🔄 اتصال مجدد';
|
| | button.onclick = () => {
|
| | this.reconnectAttempts = 0;
|
| | this.connect();
|
| | button.remove();
|
| | };
|
| |
|
| | const statusEl = document.getElementById('ws-connection-status');
|
| | if (statusEl) {
|
| | statusEl.appendChild(button);
|
| | }
|
| | }
|
| |
|
| | close() {
|
| | if (this.ws) {
|
| | this.ws.close();
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | window.wsClient = null;
|
| |
|
| |
|
| | document.addEventListener('DOMContentLoaded', () => {
|
| | try {
|
| | window.wsClient = new CryptoWebSocketClient();
|
| | console.log('✅ WebSocket Client آماده است');
|
| | } catch (error) {
|
| | console.error('❌ خطا در راهاندازی WebSocket Client:', error);
|
| | }
|
| | });
|
| |
|
| |
|