PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.7
MxChat – AI Chatbot & Content Generation for WordPress v3.1.7
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/chat-script.js +45 -135 3.2.43.1.7 View file →
@@ -60,10 +60,10 @@
60 60 return Object.keys(this.instances);
61 61 },
62 62
63 63 // Session management per bot
64 - // Returns existing session ID from cookie or localStorage (with in-memory fallback),
65 - // or null if none exists. Does NOT create a new session — use ensureSession() for that.
64 + // Returns existing session ID from cookie or localStorage, or null if none exists.
65 + // Does NOT create a new session — use ensureSession() for that.
66 66 getChatSession: function(botId) {
67 67 var cookieName = 'mxchat_session_id_' + botId;
68 68 var storageKey = 'mxchat_session_id_' + botId;
69 69 var sessionId = getCookie(cookieName);
@@ -72,21 +72,8 @@
72 72 if (!sessionId) {
73 73 try { sessionId = localStorage.getItem(storageKey); } catch (e) {}
74 74 }
75 75
76 - // Fallback to in-memory instance when cookie AND localStorage are both blocked
77 - // (Safari ITP, strict tracking prevention, cross-origin iframes with partitioned
78 - // storage). Without this, ensureSession() can generate and store an ID that
79 - // getChatSession() then can't read back, causing null session_ids on send.
80 - if (!sessionId && this.instances[botId] && this.instances[botId].sessionId) {
81 - sessionId = this.instances[botId].sessionId;
82 - }
83 -
84 - // Guard against stored sentinel values that indicate earlier broken writes.
85 - if (sessionId === 'null' || sessionId === 'undefined') {
86 - sessionId = null;
87 - }
88 -
89 76 // Re-sync cookie from localStorage if cookie was lost
90 77 if (sessionId && !getCookie(cookieName)) {
91 78 document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
92 79 }
@@ -118,10 +105,12 @@
118 105 // Now that we have a session, do the deferred work
119 106 refreshNonceIfNeeded();
120 107 trackOriginatingPage();
121 108
122 - // Note: loadChatHistory is handled by showChatContainerForBot with loader UI,
123 - // so we do NOT call it here to avoid a race condition.
109 + var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
110 + if (chatPersistenceEnabled && mxchatChat.email_collection_enabled !== 'on') {
111 + loadChatHistory(botId);
112 + }
124 113
125 114 return instance.sessionId;
126 115 },
127 116
@@ -612,23 +601,13 @@
612 601
613 602 // Get instance for session start timestamp (used when persistence is OFF)
614 603 var instance = MxChatInstances.get(botId);
615 604
616 - // Guarantee a non-null session_id before the AJAX leaves. ensureSession() is idempotent
617 - // and returns the guaranteed-present session id from the in-memory instance even when
618 - // cookie/localStorage writes are silently blocked by the browser.
619 - var sessionId = MxChatInstances.ensureSession(botId);
620 - if (!sessionId || sessionId === 'null' || sessionId === 'undefined') {
621 - // Last-resort generation to ensure we never POST a null marker.
622 - sessionId = generateSessionId();
623 - MxChatInstances.setChatSession(botId, sessionId);
624 - }
625 -
626 605 // Prepare AJAX data
627 606 const ajaxData = {
628 607 action: 'mxchat_handle_chat_request',
629 608 message: message,
630 - session_id: sessionId,
609 + session_id: getChatSession(botId),
631 610 nonce: mxchatChat.nonce,
632 611 current_page_url: window.location.href,
633 612 current_page_title: document.title,
634 613 bot_id: botId,
@@ -861,22 +840,12 @@
861 840
862 841 // Get instance for session start timestamp (used when persistence is OFF)
863 842 var instance = MxChatInstances.get(botId);
864 843
865 - // Guarantee a non-null session_id before the fetch. FormData.append() stringifies any
866 - // non-string value via String(), so passing `null` would POST the literal string "null"
867 - // and land in the transcripts table as a ghost session. ensureSession() always returns
868 - // a real string even when cookies/localStorage are blocked.
869 - var streamSessionId = MxChatInstances.ensureSession(botId);
870 - if (!streamSessionId || streamSessionId === 'null' || streamSessionId === 'undefined') {
871 - streamSessionId = generateSessionId();
872 - MxChatInstances.setChatSession(botId, streamSessionId);
873 - }
874 -
875 844 const formData = new FormData();
876 845 formData.append('action', 'mxchat_stream_chat');
877 846 formData.append('message', message);
878 - formData.append('session_id', streamSessionId);
847 + formData.append('session_id', getChatSession(botId));
879 848 formData.append('nonce', mxchatChat.nonce);
880 849 formData.append('current_page_url', window.location.href);
881 850 formData.append('current_page_title', document.title);
882 851 formData.append('bot_id', botId);
@@ -976,16 +945,8 @@
976 945
977 946 // Re-enable chat input when stream ends with content
978 947 enableChatInput(botId);
979 948
980 - // Scroll the user's last message to the top now that the
981 - // bot's full reply has rendered (gives max reading room).
982 - var $chatBoxDone = getElement(botId, 'chat-box');
983 - var $lastUserMsgDone = $chatBoxDone.find('.user-message').last();
984 - if ($lastUserMsgDone.length) {
985 - scrollElementToTop($lastUserMsgDone, botId);
986 - }
987 -
988 949 if (callback) {
989 950 callback(accumulatedContent);
990 951 }
991 952 return;
@@ -1008,16 +969,8 @@
1008 969
1009 970 // Re-enable chat input after streaming completes
1010 971 enableChatInput(botId);
1011 972
1012 - // Scroll the user's last message to the top now
1013 - // that the bot's full reply has rendered.
1014 - var $chatBoxStreamDone = getElement(botId, 'chat-box');
1015 - var $lastUserMsgStreamDone = $chatBoxStreamDone.find('.user-message').last();
1016 - if ($lastUserMsgStreamDone.length) {
1017 - scrollElementToTop($lastUserMsgStreamDone, botId);
1018 - }
1019 -
1020 973 if (callback) {
1021 974 callback(accumulatedContent);
1022 975 }
1023 976 return;
@@ -2022,14 +1975,13 @@
2022 1975 requestAnimationFrame(smoothScroll);
2023 1976 }
2024 1977 }
2025 1978
2026 - function scrollElementToTop(element, botId, topOffset) {
1979 + function scrollElementToTop(element, botId) {
2027 1980 botId = botId || 'default';
2028 - topOffset = (typeof topOffset === 'number') ? topOffset : 2;
2029 1981 var chatBox = getElement(botId, 'chat-box');
2030 1982 var elementTop = element.position().top + chatBox.scrollTop();
2031 - chatBox.animate({ scrollTop: Math.max(0, elementTop - topOffset) }, 500);
1983 + chatBox.animate({ scrollTop: elementTop }, 500);
2032 1984 }
2033 1985
2034 1986 function showChatWidget(botId) {
2035 1987 botId = botId || 'default';
@@ -2340,19 +2292,9 @@
2340 2292 var content = message.content;
2341 2293 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2342 2294 content = decodeHTMLEntities(content);
2343 2295
2344 - // Skip linkify for messages containing structured HTML
2345 - // (forms, product cards, galleries, etc.) to avoid
2346 - // markdown formatting corrupting HTML attributes
2347 - // (e.g. underscores in name="field_name" becoming <em> tags)
2348 - if (content.includes("mxchat-product-card") ||
2349 - content.includes("mxchat-image-gallery") ||
2350 - content.includes("mxchat-featured-products") ||
2351 - content.includes("<form") ||
2352 - content.includes("<input") ||
2353 - content.includes("<select") ||
2354 - content.includes("<textarea")) {
2296 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2355 2297 messageElement.html(content);
2356 2298 } else {
2357 2299 var formattedContent = linkify(content);
2358 2300 messageElement.html(formattedContent);
@@ -2686,12 +2628,8 @@
2686 2628 var instance = MxChatInstances.get(botId);
2687 2629 if (emailBlocker && !instance.emailCheckDone) {
2688 2630 instance.emailCheckDone = true;
2689 2631 resolveEmailState(botId);
2690 - } else if (!emailBlocker) {
2691 - // No email collection — still route through showChatContainerForBot
2692 - // so the loader is shown while chat history loads
2693 - showChatContainerForBot(botId);
2694 2632 }
2695 2633 } else {
2696 2634 $chatbot.removeClass('visible').addClass('hidden');
2697 2635 $(this).removeClass('hidden');
@@ -2913,59 +2851,8 @@
2913 2851 });
2914 2852
2915 2853
2916 2854 // ====================================
2917 -// INIT LOADER & CHAT CONTAINER HELPERS
2918 -// ====================================
2919 -// These must be outside the email collection block so they're always available
2920 -// (used by persistence loading even when email collection is off)
2921 -
2922 -function showInitLoader(botId) {
2923 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2924 - if (loader) loader.style.display = 'flex';
2925 -}
2926 -
2927 -function hideInitLoader(botId) {
2928 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2929 - if (loader) loader.style.display = 'none';
2930 -}
2931 -
2932 -function showEmailFormForBot(botId) {
2933 - hideInitLoader(botId);
2934 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2935 - var chatContainer = getElementDOM(botId, 'chat-container');
2936 - if (emailBlocker) emailBlocker.style.display = 'flex';
2937 - if (chatContainer) chatContainer.style.display = 'none';
2938 -}
2939 -
2940 -function showChatContainerForBot(botId) {
2941 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2942 - var chatContainer = getElementDOM(botId, 'chat-container');
2943 - if (emailBlocker) emailBlocker.style.display = 'none';
2944 -
2945 - var instance = MxChatInstances.get(botId);
2946 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2947 -
2948 - // If persistence is on and history hasn't loaded yet, show loader
2949 - // while history loads to prevent flash of empty chat
2950 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2951 - if (chatContainer) chatContainer.style.display = 'none';
2952 - showInitLoader(botId);
2953 - loadChatHistory(botId, function() {
2954 - hideInitLoader(botId);
2955 - if (chatContainer) chatContainer.style.display = 'flex';
2956 - scrollToBottom(botId, true);
2957 - });
2958 - } else {
2959 - hideInitLoader(botId);
2960 - if (chatContainer) chatContainer.style.display = 'flex';
2961 - if (typeof loadChatHistory === 'function') {
2962 - loadChatHistory(botId);
2963 - }
2964 - }
2965 -}
2966 -
2967 -// ====================================
2968 2855 // EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2969 2856 // ====================================
2970 2857 // Only run email collection setup if it's enabled
2971 2858 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
@@ -3001,8 +2888,40 @@
3001 2888 `;
3002 2889 document.head.appendChild(style);
3003 2890 }
3004 2891
2892 + // Helper functions for email collection (multi-instance aware)
2893 + function showEmailFormForBot(botId) {
2894 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2895 + var chatContainer = getElementDOM(botId, 'chat-container');
2896 + if (emailBlocker) emailBlocker.style.display = 'flex';
2897 + if (chatContainer) chatContainer.style.display = 'none';
2898 + }
2899 +
2900 + function showChatContainerForBot(botId) {
2901 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2902 + var chatContainer = getElementDOM(botId, 'chat-container');
2903 + if (emailBlocker) emailBlocker.style.display = 'none';
2904 +
2905 + var instance = MxChatInstances.get(botId);
2906 + var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2907 +
2908 + // If persistence is on and history hasn't loaded yet, keep container
2909 + // hidden until history loads to prevent flash of empty chat
2910 + if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2911 + if (chatContainer) chatContainer.style.display = 'none';
2912 + loadChatHistory(botId, function() {
2913 + if (chatContainer) chatContainer.style.display = 'flex';
2914 + scrollToBottom(botId, true);
2915 + });
2916 + } else {
2917 + if (chatContainer) chatContainer.style.display = 'flex';
2918 + if (typeof loadChatHistory === 'function') {
2919 + loadChatHistory(botId);
2920 + }
2921 + }
2922 + }
2923 +
3005 2924 function isValidEmailAddress(email) {
3006 2925 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3007 2926 return emailRegex.test(email.trim()) && email.length <= 254;
3008 2927 }
@@ -3140,14 +3059,13 @@
3140 3059
3141 3060 function checkSessionAndEmailForBot(botId) {
3142 3061 const sessionId = MxChatInstances.ensureSession(botId);
3143 3062
3144 - // Hide both panels while we check — show loader instead
3063 + // Hide both panels while we check — prevents flash of wrong state
3145 3064 var emailBlocker = getElementDOM(botId, 'email-blocker');
3146 3065 var chatContainer = getElementDOM(botId, 'chat-container');
3147 3066 if (emailBlocker) emailBlocker.style.display = 'none';
3148 3067 if (chatContainer) chatContainer.style.display = 'none';
3149 - showInitLoader(botId);
3150 3068
3151 3069 fetch(mxchatChat.ajax_url, {
3152 3070 method: 'POST',
3153 3071 headers: {
@@ -3327,8 +3245,9 @@
3327 3245 $('.mxchat-chatbot-wrapper').each(function() {
3328 3246 var botId = $(this).data('bot-id') || 'default';
3329 3247 var emailBlocker = getElementDOM(botId, 'email-blocker');
3330 3248
3249 + // Only check if email blocker exists for this bot
3331 3250 if (emailBlocker) {
3332 3251 if (isEmbeddedBot(botId)) {
3333 3252 // Embedded bots are always visible — check now
3334 3253 resolveEmailState(botId);
@@ -3333,15 +3252,8 @@
3333 3252 // Embedded bots are always visible — check now
3334 3253 resolveEmailState(botId);
3335 3254 }
3336 3255 // Floating bots: handled in the widget open handler
3337 - } else if (isEmbeddedBot(botId)) {
3338 - // Embedded bot, no email collection — load history with loader
3339 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3340 - if (chatPersistenceEnabled) {
3341 - MxChatInstances.ensureSession(botId);
3342 - showChatContainerForBot(botId);
3343 - }
3344 3256 }
3345 3257 });
3346 3258 }
3347 3259
@@ -3366,10 +3278,8 @@
3366 3278 var instance = MxChatInstances.get(botId);
3367 3279 if (emailBlocker && !instance.emailCheckDone) {
3368 3280 instance.emailCheckDone = true;
3369 3281 resolveEmailState(botId);
3370 - } else if (!emailBlocker) {
3371 - showChatContainerForBot(botId);
3372 3282 }
3373 3283 }
3374 3284 });
3375 3285