1/** 2 * BIC Avaya - Chat WebSocket Handler 3 * 4 * Manages WebSocket connection for live chat. 5 * 6 * @package BIC_Avaya 7 */ 8 9(function($, window) { 10 'use strict'; 11 12 if (typeof bicAvayaChatConfig === 'undefined') { 13 bicAvayaLog.error('Chat config not loaded'); 14 return; 15 } 16 17 /** 18 * WebSocket manager 19 */ 20 window.bicAvayaChatSocket = { 21 socket: null, 22 retryCount: 0, 23 pingInterval: null, 24 dontRetryConnection: false, 25 isConnected: false, 26 messageHandlers: {}, 27 28 /** 29 * Connect to the WebSocket server 30 */ 31 connect: function() { 32 var self = this; 33 34 if (this.socket && this.socket.readyState === WebSocket.OPEN) { 35 bicAvayaLog.debug('WebSocket already connected'); 36 return; 37 } 38 39 bicAvayaLog.debug('Connecting to WebSocket:', bicAvayaChatConfig.webSocketUrl); 40 41 try { 42 this.socket = new WebSocket(bicAvayaChatConfig.webSocketUrl); 43 44 this.socket.onopen = function() { 45 bicAvayaLog.info('â WebSocket connection established'); 46 bicAvayaLog.debug('Ready state:', self.socket.readyState); 47 self.isConnected = true; 48 self.retryCount = 0; 49 self.startPing(); 50 self.trigger('connected'); 51 }; 52 53 this.socket.onclose = function(event) { 54 bicAvayaLog.debug('WebSocket closed', event.code, event.reason); 55 self.isConnected = false; 56 self.stopPing(); 57 self.trigger('disconnected'); 58 59 // Auto-reconnect unless explicitly closed or max retries reached 60 if (!self.dontRetryConnection && event.code !== 1000) { 61 self.scheduleReconnect(); 62 } 63 }; 64 65 this.socket.onerror = function(error) { 66 bicAvayaLog.error('WebSocket error:', error); 67 self.trigger('error', error); 68 }; 69 70 this.socket.onmessage = function(event) { 71 self.handleMessage(event.data); 72 }; 73 74 } catch (e) { 75 bicAvayaLog.error('Failed to create WebSocket:', e); 76 this.scheduleReconnect(); 77 } 78 }, 79 80 /** 81 * Disconnect from the WebSocket server 82 */ 83 disconnect: function() { 84 this.dontRetryConnection = true; 85 this.stopPing(); 86 87 if (this.socket) { 88 this.socket.close(1000, 'User closed chat'); 89 this.socket = null; 90 } 91 92 this.isConnected = false; 93 bicAvayaLog.debug('WebSocket disconnected'); 94 }, 95 96 /** 97 * Schedule a reconnection attempt 98 */ 99 scheduleReconnect: function() { 100 var self = this; 101 102 if (this.retryCount >= bicAvayaChatConfig.maxRetries) { 103 bicAvayaLog.error('Max reconnection attempts reached'); 104 this.trigger('maxRetriesReached'); 105 return; 106 } 107 108 this.retryCount++; 109 bicAvayaLog.debug('Scheduling reconnect attempt', this.retryCount); 110 111 setTimeout(function() { 112 if (!self.dontRetryConnection) { 113 self.connect(); 114 } 115 }, bicAvayaChatConfig.retryInterval); 116 }, 117 118 /** 119 * Send a message through the WebSocket 120 */ 121 send: function(message) { 122 if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { 123 bicAvayaLog.error('Cannot send message - WebSocket not connected'); 124 return false; 125 } 126 127 var messageStr = typeof message === 'string' ? message : JSON.stringify(message); 128 129 bicAvayaLog.info('â Sending WebSocket message:', message); 130 bicAvayaLog.debug('â JSON string:', messageStr); 131 this.socket.send(messageStr); 132 return true; 133 }, 134 135 /** 136 * Handle incoming WebSocket message 137 */ 138 handleMessage: function(data) { 139 bicAvayaLog.info('â Received WebSocket message (raw):', data); 140 141 try { 142 var message = JSON.parse(data); 143 bicAvayaLog.info('â Parsed message object:', message); 144 145 var type = message.type || message.messageType; 146 bicAvayaLog.info('â Detected message type: "' + type + '"'); 147 bicAvayaLog.info('â Expected NEW_CHAT_ACK type: "' + bicAvayaChatConfig.messageTypes.NEW_CHAT_ACK + '"'); 148 bicAvayaLog.info('â Type match:', type === bicAvayaChatConfig.messageTypes.NEW_CHAT_ACK); 149 150 switch (type) { 151 case bicAvayaChatConfig.messageTypes.NEW_CHAT_ACK: 152 bicAvayaLog.info('âââ Matched NEW_CHAT_ACK - triggering chatCreated event âââ'); 153 this.trigger('chatCreated', message); 154 break; 155 156 case bicAvayaChatConfig.messageTypes.ACK: 157 bicAvayaLog.debug('â Acknowledgement received'); 158 this.trigger('acknowledgement', message); 159 break; 160 161 case bicAvayaChatConfig.messageTypes.ERROR: 162 bicAvayaLog.error('â Server error:', message.body); 163 this.trigger('chatError', message); 164 break; 165 166 case bicAvayaChatConfig.messageTypes.NOTIFICATION: 167 bicAvayaLog.debug('â Notification:', message.body && message.body.method); 168 this.handleNotification(message); 169 break; 170 171 default: 172 bicAvayaLog.warn('â Unknown/unhandled message type: "' + type + '"'); 173 bicAvayaLog.warn('â Full message:', message); 174 this.trigger('message', message); 175 } 176 } catch (e) { 177 bicAvayaLog.error('Failed to parse message:', e, data); 178 } 179 }, 180 181 /** 182 * Handle notification messages 183 */ 184 handleNotification: function(message) { 185 var body = message.body || {}; 186 var method = body.method; 187 188 bicAvayaLog.debug('â Notification method: "' + method + '"'); 189 190 switch (method) { 191 case 'requestChat': 192 // Chat successfully created - this is the acknowledgement 193 bicAvayaLog.info('âââ Chat created via notification (requestChat) âââ'); 194 this.trigger('chatCreated', message); 195 break; 196 197 case 'newMessage': 198 // Agent sent a message 199 this.trigger('agentMessage', { 200 message: body.message, 201 displayName: body.displayName || 'Agent', 202 timestamp: body.timestamp 203 }); 204 break; 205 206 case 'newParticipant': 207 // Agent joined 208 this.trigger('participants', { 209 participants: body.participants || [], 210 displayName: body.displayName, 211 agentId: body.agentId 212 }); 213 break; 214 215 case 'N': 216 case 'N_PLAIN': 217 case 'N_RICH': 218 this.trigger('agentMessage', { 219 message: body.value, 220 displayName: body.displayName || 'Agent', 221 isRich: method === 'N_RICH' 222 }); 223 break; 224 225 case 'N_AGENT_TYPING_START': 226 this.trigger('agentTyping', { isTyping: true }); 227 break; 228 229 case 'N_AGENT_TYPING_STOP': 230 this.trigger('agentTyping', { isTyping: false }); 231 break; 232 233 case 'isTyping': 234 // Handle isTyping notifications from agent 235 if (body.isTyping === true) { 236 this.trigger('agentTyping', { isTyping: true }); 237 } else if (body.isTyping === false) { 238 this.trigger('agentTyping', { isTyping: false }); 239 } 240 break; 241 242 case 'N_QUEUE_POSITION': 243 this.trigger('queuePosition', { position: body.value }); 244 break; 245 246 case 'N_PARTICIPANTS': 247 this.trigger('participants', { participants: body.value }); 248 break; 249 250 case 'N_END_CHAT': 251 case 'N_CLOSE': 252 case 'closeConversation': 253 this.trigger('chatEnded', { reason: body.value || 'Chat ended' }); 254 break; 255 256 case 'N_PAGE_PUSH': 257 this.trigger('pagePush', { url: body.value }); 258 break; 259 260 case 'ping': 261 // Ping response - ignore 262 break; 263 264 default: 265 bicAvayaLog.debug('Unknown notification method:', method); 266 this.trigger('notification', message); 267 } 268 }, 269 270 /** 271 * Start ping interval to keep connection alive 272 */ 273 startPing: function() { 274 var self = this; 275 this.stopPing(); 276 277 this.pingInterval = setInterval(function() { 278 if (self.isConnected) { 279 self.send({ 280 apiVersion: '1.0', 281 type: 'request', 282 body: { 283 method: 'ping' 284 } 285 }); 286 } 287 }, bicAvayaChatConfig.pingTimer); 288 }, 289 290 /** 291 * Stop ping interval 292 */ 293 stopPing: function() { 294 if (this.pingInterval) { 295 clearInterval(this.pingInterval); 296 this.pingInterval = null; 297 } 298 }, 299 300 /** 301 * Register an event handler 302 */ 303 on: function(event, handler) { 304 if (!this.messageHandlers[event]) { 305 this.messageHandlers[event] = []; 306 } 307 this.messageHandlers[event].push(handler); 308 }, 309 310 /** 311 * Remove an event handler 312 */ 313 off: function(event, handler) { 314 if (!this.messageHandlers[event]) return; 315 316 if (handler) { 317 this.messageHandlers[event] = this.messageHandlers[event].filter(function(h) { 318 return h !== handler; 319 }); 320 } else { 321 delete this.messageHandlers[event]; 322 } 323 }, 324 325 /** 326 * Trigger an event 327 */ 328 trigger: function(event, data) { 329 var handlers = this.messageHandlers[event]; 330 if (!handlers) return; 331
332 handlers.forEach(function(handler) { 333 try { 334 handler(data); 335 } catch (e) { 336 bicAvayaLog.error('Error in event handler:', event, e); 337 } 338 }); 339 }, 340 341 /** 342 * Reset socket state for new chat 343 */ 344 reset: function() { 345 this.dontRetryConnection = false; 346 this.retryCount = 0; 347 } 348 }; 349 350 bicAvayaLog.debug('Chat socket module loaded'); 351 352})(jQuery, window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.