1
0

websocket.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. /**
  2. * WebSocket client for real-time updates
  3. */
  4. class WebSocketClient {
  5. constructor(basePath = '') {
  6. this.basePath = basePath;
  7. this.ws = null;
  8. this.reconnectAttempts = 0;
  9. this.maxReconnectAttempts = 10;
  10. this.reconnectDelay = 1000;
  11. this.listeners = new Map();
  12. this.isConnected = false;
  13. this.shouldReconnect = true;
  14. }
  15. connect() {
  16. if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
  17. return;
  18. }
  19. this.shouldReconnect = true;
  20. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  21. // Ensure basePath ends with '/' for proper URL construction
  22. let basePath = this.basePath || '';
  23. if (basePath && !basePath.endsWith('/')) {
  24. basePath += '/';
  25. }
  26. const wsUrl = `${protocol}//${window.location.host}${basePath}ws`;
  27. console.log('WebSocket connecting to:', wsUrl, 'basePath:', this.basePath);
  28. try {
  29. this.ws = new WebSocket(wsUrl);
  30. this.ws.onopen = () => {
  31. console.log('WebSocket connected');
  32. this.isConnected = true;
  33. this.reconnectAttempts = 0;
  34. this.emit('connected');
  35. };
  36. this.ws.onmessage = (event) => {
  37. try {
  38. // Validate message size (prevent memory issues)
  39. const maxMessageSize = 10 * 1024 * 1024; // 10MB
  40. if (event.data && event.data.length > maxMessageSize) {
  41. console.error('WebSocket message too large:', event.data.length, 'bytes');
  42. this.ws.close();
  43. return;
  44. }
  45. const message = JSON.parse(event.data);
  46. if (!message || typeof message !== 'object') {
  47. console.error('Invalid WebSocket message format');
  48. return;
  49. }
  50. this.handleMessage(message);
  51. } catch (e) {
  52. console.error('Failed to parse WebSocket message:', e);
  53. }
  54. };
  55. this.ws.onerror = (error) => {
  56. console.error('WebSocket error:', error);
  57. this.emit('error', error);
  58. };
  59. this.ws.onclose = () => {
  60. console.log('WebSocket disconnected');
  61. this.isConnected = false;
  62. this.emit('disconnected');
  63. if (this.shouldReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
  64. this.reconnectAttempts++;
  65. const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
  66. console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
  67. setTimeout(() => this.connect(), delay);
  68. }
  69. };
  70. } catch (e) {
  71. console.error('Failed to create WebSocket connection:', e);
  72. this.emit('error', e);
  73. }
  74. }
  75. handleMessage(message) {
  76. const { type, payload, time } = message;
  77. // Emit to specific type listeners
  78. this.emit(type, payload, time);
  79. // Emit to all listeners
  80. this.emit('message', { type, payload, time });
  81. }
  82. on(event, callback) {
  83. if (!this.listeners.has(event)) {
  84. this.listeners.set(event, []);
  85. }
  86. const callbacks = this.listeners.get(event);
  87. if (!callbacks.includes(callback)) {
  88. callbacks.push(callback);
  89. }
  90. }
  91. off(event, callback) {
  92. if (!this.listeners.has(event)) {
  93. return;
  94. }
  95. const callbacks = this.listeners.get(event);
  96. const index = callbacks.indexOf(callback);
  97. if (index > -1) {
  98. callbacks.splice(index, 1);
  99. }
  100. }
  101. emit(event, ...args) {
  102. if (this.listeners.has(event)) {
  103. this.listeners.get(event).forEach(callback => {
  104. try {
  105. callback(...args);
  106. } catch (e) {
  107. console.error('Error in WebSocket event handler:', e);
  108. }
  109. });
  110. }
  111. }
  112. disconnect() {
  113. this.shouldReconnect = false;
  114. if (this.ws) {
  115. this.ws.close();
  116. this.ws = null;
  117. }
  118. }
  119. send(data) {
  120. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  121. this.ws.send(JSON.stringify(data));
  122. } else {
  123. console.warn('WebSocket is not connected');
  124. }
  125. }
  126. }
  127. // Create global WebSocket client instance
  128. // Safely get basePath from global scope (defined in page.html)
  129. window.wsClient = new WebSocketClient(typeof basePath !== 'undefined' ? basePath : '');