registerSocketHandlers()

in src/components/Widget/index.js [441:586]


  registerSocketHandlers(sendInitPayload = true) {
    const {
      storage,
      socket,
      dispatch,
      connectOn,
      tooltipPayload,
      tooltipDelay,
      customData
    } = this.props;

    // Skip if handlers already registered to prevent duplicates
    if (this.socketHandlersRegistered) {
      logger.info('⏭️ Socket handlers already registered, skipping...');
      return;
    }

    logger.info('📝 Registering socket event handlers...');

    // Register bot_uttered handler
    socket.on('bot_uttered', (botUttered) => {
      this.handleBotUtterance(botUttered);
    });

    // Register connect handler - CRITICAL for session_request
    const sendSessionRequest = () => {
      // Try to get existing session_id, including preserved one during token refresh
      let localId = this.getSessionId();

      // If no session in localStorage but socket has preservedSessionId, use it
      if (!localId && socket.preservedSessionId) {
        localId = socket.preservedSessionId;
        logger.debug('Using preserved session_id for token refresh:', localId);
      }

      logger.info('📤 Sending session_request', {
        session_id: localId || 'null (requesting new)',
        hasAuth: !!customData?.auth_header,
        socketId: socket.socket?.id
      });

      // DIAGNOSTIC: Check token expiration before session_request
      if (customData?.auth_header) {
        try {
          const tokenPayload = customData.auth_header.split('.')[1];
          const decoded = JSON.parse(atob(tokenPayload.replace(/-/g, '+').replace(/_/g, '/')));
          const now = Date.now() / 1000;
          const timeLeft = decoded.exp - now;
          logger.info('🔍 SESSION_REQUEST: Access token expires in:', Math.round(timeLeft / 60), 'minutes');
          if (timeLeft < 0) {
            logger.error('❌ SESSION_REQUEST: Using EXPIRED access token! Expired', Math.round(-timeLeft / 60), 'minutes ago');
          }
        } catch (e) {
          logger.error('❌ SESSION_REQUEST: Failed to decode access token:', e);
        }
      }

      // Only include session_id if we have one, otherwise let backend create new one
      const payload = { customData };
      if (localId) {
        payload.session_id = localId;
      }

      socket.emit('session_request', payload);
    };

    socket.on('connect', sendSessionRequest);

    // CRITICAL: If socket is already connected, send session_request immediately
    if (socket.isInitialized()) {
      logger.info('🔄 Socket already connected, sending session_request immediately');
      sendSessionRequest();
    }

    // Register session_confirm handler
    socket.on('session_confirm', (sessionObject) => {
      const remoteId = (sessionObject && sessionObject.session_id)
        ? sessionObject.session_id
        : sessionObject;

      logger.info(`session_confirm:${socket.socket.id} session_id:${remoteId}`);

      // Store the initial state to both the redux store and the storage, set connected to true
      dispatch(connectServer());

      let localId = this.getSessionId();

      // If we were trying to preserve a session_id during token refresh
      if (!localId && socket.preservedSessionId) {
        localId = socket.preservedSessionId;
        logger.info(`Token refresh: requested preserved session_id ${localId}, server returned ${remoteId}`);
      }

      if (localId !== remoteId) {
        // Store the received session_id to storage
        storeLocalSession(storage, SESSION_NAME, remoteId);
        dispatch(pullSession());
        if (sendInitPayload) {
          this.trySendInitPayload();
        }
      } else {
        logger.info('Session_id preserved successfully during token refresh');

        // If this is an existing session, it's possible we changed pages and want to send a
        // user message when we land.
        const nextMessage = window.localStorage.getItem(NEXT_MESSAGE);

        if (nextMessage !== null) {
          const { message, expiry } = JSON.parse(nextMessage);
          window.localStorage.removeItem(NEXT_MESSAGE);

          if (expiry === 0 || expiry > Date.now()) {
            dispatch(addUserMessage(message));
            dispatch(emitUserMessage(message));
            dispatch(setBotProcessing(true));
            // Start 30-second timeout to reset bot processing if backend hangs
            startBotProcessingTimeout(dispatch);
          }
        }
      }

      // Clear preserved session_id after use
      if (socket.preservedSessionId) {
        delete socket.preservedSessionId;
      }

      if (connectOn === 'mount' && tooltipPayload) {
        this.tooltipTimeout = setTimeout(() => {
          this.trySendTooltipPayload();
        }, parseInt(tooltipDelay, 10));
      }
    });

    // Register disconnect handler
    socket.on('disconnect', (reason) => {
      logger.info('Disconnected:', reason);
      if (reason !== 'io client disconnect') {
        dispatch(disconnectServer());
      }
    });

    // Mark handlers as registered
    this.socketHandlersRegistered = true;

    logger.info('✅ Socket event handlers registered');
  }