Plugin Directory

Changeset 3402632


Ignore:
Timestamp:
11/25/2025 03:00:58 PM (4 months ago)
Author:
appwavedev
Message:

Update to version 1.0.6: Enhanced trigger messages system with URL-based matching, fixed ping message close button click reliability, improved mobile responsiveness

Location:
bettercx-widget/trunk
Files:
11 added
9 edited

Legend:

Unmodified
Added
Removed
  • bettercx-widget/trunk/assets/bettercx-widget.esm.js

    r3385343 r3402632  
    1 import{p as e,g as a,b as t}from"./p-BTuzHDoC.js";export{s as setNonce}from"./p-BTuzHDoC.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-73aa3697",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-339847ab",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],e))));
     1import{p as e,g as a,b as t}from"./p-BTuzHDoC.js";export{s as setNonce}from"./p-BTuzHDoC.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-73aa3697",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-43f268bb",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],e))));
  • bettercx-widget/trunk/assets/index.esm.js

    r3385343 r3402632  
    1 export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p-DOTTKS3U.js";import"./p-BTuzHDoC.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format}
     1export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p-Be7T8ATp.js";import"./p-BTuzHDoC.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format}
  • bettercx-widget/trunk/assets/p-B7XTg7r_.system.js

    r3385343 r3402632  
    1 var __awaiter=this&&this.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,o){function u(e){try{c(r.next(e))}catch(e){o(e)}}function a(e){try{c(r["throw"](e))}catch(e){o(e)}}function c(e){e.done?n(e.value):i(e.value).then(u,a)}c((r=r.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(o[0]&1)throw o[1];return o[1]},trys:[],ops:[]},r,i,o,u;return u={next:a(0),throw:a(1),return:a(2)},typeof Symbol==="function"&&(u[Symbol.iterator]=function(){return this}),u;function a(e){return function(t){return c([e,t])}}function c(a){if(r)throw new TypeError("Generator is already executing.");while(u&&(u=0,a[0]&&(n=0)),n)try{if(r=1,i&&(o=a[0]&2?i["return"]:a[0]?i["throw"]||((o=i["return"])&&o.call(i),0):i.next)&&!(o=o.call(i,a[1])).done)return o;if(i=0,o)a=[a[0]&2,o.value];switch(a[0]){case 0:case 1:o=a;break;case 4:n.label++;return{value:a[1],done:false};case 5:n.label++;i=a[1];a=[0];continue;case 7:a=n.ops.pop();n.trys.pop();continue;default:if(!(o=n.trys,o=o.length>0&&o[o.length-1])&&(a[0]===6||a[0]===2)){n=0;continue}if(a[0]===3&&(!o||a[1]>o[0]&&a[1]<o[3])){n.label=a[1];break}if(a[0]===6&&n.label<o[1]){n.label=o[1];o=a;break}if(o&&n.label<o[2]){n.label=o[2];n.ops.push(a);break}if(o[2])n.ops.pop();n.trys.pop();continue}a=t.call(e,n)}catch(e){a=[6,e];i=0}finally{r=o=0}if(a[0]&5)throw a[1];return{value:a[0]?a[1]:void 0,done:true}}};System.register(["./p-eV7FkxIV.system.js"],(function(e,t){"use strict";var n,r,i;return{setters:[function(t){n=t.p;r=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var o=function(){var e=t.meta.url;var r={};if(e!==""){r.resourcesUrl=new URL(".",e).href}return n(r)};o().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,r()];case 1:e.sent();return[2,i([["p-65f90db1.system",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-e1740149.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],t)]}}))}))}))}}}));
     1var __awaiter=this&&this.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,o){function u(e){try{c(r.next(e))}catch(e){o(e)}}function a(e){try{c(r["throw"](e))}catch(e){o(e)}}function c(e){e.done?n(e.value):i(e.value).then(u,a)}c((r=r.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(o[0]&1)throw o[1];return o[1]},trys:[],ops:[]},r,i,o,u;return u={next:a(0),throw:a(1),return:a(2)},typeof Symbol==="function"&&(u[Symbol.iterator]=function(){return this}),u;function a(e){return function(t){return c([e,t])}}function c(a){if(r)throw new TypeError("Generator is already executing.");while(u&&(u=0,a[0]&&(n=0)),n)try{if(r=1,i&&(o=a[0]&2?i["return"]:a[0]?i["throw"]||((o=i["return"])&&o.call(i),0):i.next)&&!(o=o.call(i,a[1])).done)return o;if(i=0,o)a=[a[0]&2,o.value];switch(a[0]){case 0:case 1:o=a;break;case 4:n.label++;return{value:a[1],done:false};case 5:n.label++;i=a[1];a=[0];continue;case 7:a=n.ops.pop();n.trys.pop();continue;default:if(!(o=n.trys,o=o.length>0&&o[o.length-1])&&(a[0]===6||a[0]===2)){n=0;continue}if(a[0]===3&&(!o||a[1]>o[0]&&a[1]<o[3])){n.label=a[1];break}if(a[0]===6&&n.label<o[1]){n.label=o[1];o=a;break}if(o&&n.label<o[2]){n.label=o[2];n.ops.push(a);break}if(o[2])n.ops.pop();n.trys.pop();continue}a=t.call(e,n)}catch(e){a=[6,e];i=0}finally{r=o=0}if(a[0]&5)throw a[1];return{value:a[0]?a[1]:void 0,done:true}}};System.register(["./p-eV7FkxIV.system.js"],(function(e,t){"use strict";var n,r,i;return{setters:[function(t){n=t.p;r=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var o=function(){var e=t.meta.url;var r={};if(e!==""){r.resourcesUrl=new URL(".",e).href}return n(r)};o().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,r()];case 1:e.sent();return[2,i([["p-65f90db1.system",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-887c5563.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],t)]}}))}))}))}}}));
  • bettercx-widget/trunk/bettercx-widget.php

    r3385343 r3402632  
    44 * Plugin URI: https://wordpress.org/plugins/bettercx-widget/
    55 * Description: Professional AI-powered chat widget for BetterCX platform. Seamlessly integrate intelligent customer support into any website with full WordPress compatibility. Fully functional out of the box with no trial limitations.
    6  * Version: 1.0.5
     6 * Version: 1.0.6
    77 * Author: BetterCX
    88 * Author URI: https://bettercx.ai
     
    1616 *
    1717 * @package BetterCX_Widget
    18  * @version 1.0.5
     18 * @version 1.0.6
    1919 * @author BetterCX
    2020 * @license GPLv2+
     
    3737
    3838// Define plugin constants
    39 define('BETTERCX_WIDGET_VERSION', '1.0.5');
     39define('BETTERCX_WIDGET_VERSION', '1.0.6');
    4040define('BETTERCX_WIDGET_PLUGIN_FILE', __FILE__);
    4141define('BETTERCX_WIDGET_PLUGIN_DIR', plugin_dir_path(__FILE__));
  • bettercx-widget/trunk/readme.txt

    r3385343 r3402632  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.0.5
     7Stable tag: 1.0.6
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    244244
    245245== Changelog ==
     246
     247= 1.0.6 =
     248* Added support for multiple trigger messages with URL-based matching
     249* Trigger messages now automatically selected based on current page URL
     250* Improved default message handling - only displays on matching website origins
     251* Fixed ping message close button click reliability on desktop and mobile
     252* Removed rotation animation from close button for cleaner interaction
     253* Enhanced touch event handling for better mobile responsiveness
     254* Improved event propagation to prevent click blocking issues
     255* Simplified close button styling for better accessibility
    246256
    247257= 1.0.5 =
     
    301311== Upgrade Notice ==
    302312
     313= 1.0.6 =
     314Important update: Enhanced trigger messages system with URL-based matching. Fixed mobile and desktop click issues on the ping message close button. Improved overall reliability and user experience.
     315
    303316= 1.0.5 =
    304317Product parsing update: Added markdown-style product link parsing from streamed messages. Fixed mobile click issues and improved text formatting after product removal.
     
    545558
    546559= Last Updated =
    547 2024-01-15
     5602025-11-25
    548561
    549562= Version =
    550 1.0.5
     5631.0.6
    551564
    552565= Minimum WordPress Version =
     
    563576
    564577= Stable Tag =
    565 1.0.5
     5781.0.6
    566579
    567580= Development Version =
    568 1.0.5
     5811.0.6
    569582
    570583= Requires at least =
  • bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.tsx

    r3385343 r3402632  
    9393    if (this.products.length <= 1) return;
    9494
    95     this.isDragging = true;
    96     this.startX = e.touches[0].clientX;
     95    // Only start dragging if touch moves (don't interfere with click)
     96    const touch = e.touches[0];
     97    this.startX = touch.clientX;
    9798    this.initialTranslate = this.getCurrentTranslate();
    98     e.preventDefault();
     99    // Don't set isDragging yet - wait for touchmove
    99100  };
    100101
    101102  private handleTouchMove = (e: TouchEvent) => {
    102     if (!this.isDragging || this.products.length <= 1) return;
     103    if (this.products.length <= 1) return;
    103104
    104105    this.currentX = e.touches[0].clientX;
    105     const diff = this.currentX - this.startX;
     106    const diff = Math.abs(this.currentX - this.startX);
     107
     108    // Only start dragging if touch moves significantly (to distinguish from click)
     109    if (!this.isDragging && diff > 10) {
     110      this.isDragging = true;
     111    }
     112
     113    if (!this.isDragging) return;
     114
    106115    const containerWidth = this.sliderRef.parentElement?.offsetWidth || 0;
    107     const diffPercentage = (diff / containerWidth) * 100;
     116    const diffPercentage = ((this.currentX - this.startX) / containerWidth) * 100;
    108117    const newTranslate = this.initialTranslate + diffPercentage;
    109118
     
    113122
    114123  private handleTouchEnd = () => {
    115     if (!this.isDragging || this.products.length <= 1) return;
    116 
     124    if (this.products.length <= 1) return;
     125
     126    const wasDragging = this.isDragging;
    117127    this.isDragging = false;
    118     this.snapToNearestSlide();
     128
     129    // Only snap to slide if user was actually dragging
     130    if (wasDragging) {
     131      this.snapToNearestSlide();
     132    }
    119133  };
    120134
     
    196210  }
    197211
    198   private handleProductClick = (product: Product) => {
    199     // Open product URL in new tab
    200     window.open(product.product_url, '_blank', 'noopener,noreferrer');
     212  private handleProductClick = (product: Product, e: Event) => {
     213    // Prevent event from bubbling to prevent conflict with touch handlers
     214    e.stopPropagation();
     215
     216    // Only navigate if not dragging (user is actually clicking, not swiping)
     217    if (!this.isDragging) {
     218      // Open product URL in new tab
     219      window.open(product.product_url, '_blank', 'noopener,noreferrer');
     220    }
    201221  };
    202222
     
    233253          >
    234254            {this.products.map((product, index) => (
    235               <div key={index} class="bcx-product-slider__card" style={{ width: `${100 / this.products.length}%` }} onClick={() => this.handleProductClick(product)}>
     255              <div key={index} class="bcx-product-slider__card" style={{ width: `${100 / this.products.length}%` }} onClick={e => this.handleProductClick(product, e)}>
    236256                <div class="bcx-product-slider__card-image">
    237257                  <img
  • bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.scss

    r3385343 r3402632  
    19531953.bcx-widget__ping-close {
    19541954  position: absolute;
    1955   top: 0px;
    1956   right: 0px;
    1957   transform: translate(25%, -25%);
    1958   width: 26px;
    1959   height: 26px;
     1955  top: var(--bcx-space-2);
     1956  right: var(--bcx-space-2);
     1957  width: 28px;
     1958  height: 28px;
     1959  min-width: 28px;
     1960  min-height: 28px;
    19601961  border: none;
    1961   background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
    1962   color: white;
     1962  background: color-mix(in srgb, var(--bcx-text-primary) 8%, transparent);
     1963  color: var(--bcx-text-secondary);
    19631964  cursor: pointer;
    1964   border-radius: 50%;
     1965  border-radius: var(--bcx-radius-full);
    19651966  display: flex;
    19661967  align-items: center;
    19671968  justify-content: center;
    1968   transition: all var(--bcx-transition-normal);
     1969  transition:
     1970    background-color var(--bcx-transition-fast),
     1971    color var(--bcx-transition-fast);
    19691972  z-index: 10;
    1970 
    1971   /* Elegant hover effect */
     1973  touch-action: manipulation;
     1974  -webkit-tap-highlight-color: transparent;
     1975  padding: 0;
     1976  margin: 0;
     1977  pointer-events: auto;
     1978
     1979  /* Simple hover effect */
    19721980  &:hover {
    1973     background: linear-gradient(135deg, #ff5252, #ff7979);
    1974     transform: translateY(-1px) scale(1.05) translate(25%, -25%);
     1981    background: color-mix(in srgb, var(--bcx-text-primary) 12%, transparent);
     1982    color: var(--bcx-text-primary);
    19751983  }
    19761984
    19771985  /* Active state */
    19781986  &:active {
    1979     transform: translateY(0) scale(0.95);
    1980     transition: all var(--bcx-transition-fast);
    1981   }
    1982 
    1983   /* Focus state */
     1987    background: color-mix(in srgb, var(--bcx-text-primary) 16%, transparent);
     1988  }
     1989
     1990  /* Focus state - simple outline, no transform */
    19841991  &:focus {
    1985     outline: 2px solid rgba(255, 107, 107, 0.4);
     1992    outline: 2px solid color-mix(in srgb, var(--bcx-primary-500) 30%, transparent);
    19861993    outline-offset: 2px;
     1994  }
     1995
     1996  /* Focus visible for keyboard navigation */
     1997  &:focus:not(:focus-visible) {
     1998    outline: none;
    19871999  }
    19882000
    19892001  /* Icon styling */
    19902002  svg {
    1991     width: 14px;
    1992     height: 14px;
    1993     stroke-width: 2.5;
    1994     transition: transform var(--bcx-transition-fast);
    1995   }
    1996 
    1997   &:hover svg {
    1998     transform: rotate(90deg);
     2003    width: 16px;
     2004    height: 16px;
     2005    stroke-width: 2;
     2006    flex-shrink: 0;
     2007    pointer-events: none;
    19992008  }
    20002009}
  • bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.tsx

    r3385343 r3402632  
    44import { ThemeService } from '../../services/theme.service';
    55import { WidgetState, WidgetEvent, ChatMessage, Product } from '../../types/api';
     6import { parseProducts, removeProductsFromText } from '../../utils/product-parser';
    67
    78@Component({
     
    135136      }
    136137
    137       const triggerMessage = ('trigger_message' in sessionData ? sessionData.trigger_message : undefined) as string | undefined;
     138      const triggerMessages = ('trigger_messages' in sessionData ? sessionData.trigger_messages : []) as Array<{
     139        id: number;
     140        url: string;
     141        message: string;
     142        is_default: boolean;
     143        trigger_after_seconds: number;
     144        created_at: string;
     145        updated_at: string;
     146      }>;
    138147      const agentName = ('agent_name' in sessionData ? sessionData.agent_name : undefined) as string | undefined;
     148
     149      // Select the appropriate trigger message based on current URL
     150      const selectedTriggerMessage = this.selectTriggerMessage(triggerMessages);
    139151
    140152      this.setState({
     
    150162        logo: ('logo' in sessionData ? sessionData.logo : undefined) as string | undefined,
    151163        welcomeMessage: welcomeMessage,
    152         triggerMessage: triggerMessage,
     164        triggerMessages: triggerMessages,
     165        selectedTriggerMessage: selectedTriggerMessage,
    153166        agentName: agentName,
    154167        messages: initialMessages,
     
    156169
    157170      // Start ping message timer if trigger message exists
    158       if (triggerMessage && triggerMessage.trim()) {
    159         this.startPingMessageTimer();
     171      if (selectedTriggerMessage && selectedTriggerMessage.message && selectedTriggerMessage.message.trim()) {
     172        this.startPingMessageTimer(selectedTriggerMessage);
    160173      }
    161174      this.emitEvent('session-created', { origin });
     
    271284
    272285            if (assistantMessage) {
     286              // Just accumulate content during streaming
    273287              assistantMessage.content += chunk.content;
     288
    274289              this.setState({
    275290                messages: [...this.state.messages.slice(0, -1), { ...assistantMessage }],
     
    300315              this.setState({ isTyping: true });
    301316            }
     317          }
     318        }
     319
     320        // After streaming is complete, parse products from full content
     321        if (assistantMessage) {
     322          // Parse products from the complete content
     323          const parsedProducts = parseProducts(assistantMessage.content);
     324
     325          if (parsedProducts.length > 0) {
     326            if (!assistantMessage.products) {
     327              assistantMessage.products = [];
     328            }
     329            // Convert ProductInfo to Product format and add to message
     330            const products = parsedProducts.map(p => ({
     331              product_name: p.product_name,
     332              image_url: p.image_url,
     333              product_url: p.product_url,
     334            }));
     335            assistantMessage.products = [...assistantMessage.products, ...products];
     336
     337            // Remove product markdown from content
     338            assistantMessage.content = removeProductsFromText(assistantMessage.content);
    302339          }
    303340        }
     
    599636      >
    600637        {/* Ping Message */}
    601         {this.state.showPingMessage && this.state.triggerMessage && (
     638        {this.state.showPingMessage && this.state.selectedTriggerMessage && this.state.selectedTriggerMessage.message && (
    602639          <div class="bcx-widget__ping-message" data-adblock-bypass="true">
    603640            <div class="bcx-widget__ping-content">
     
    616653              )}
    617654              <div class="bcx-widget__ping-text">
    618                 <div class="bcx-widget__ping-message-text">{this.state.triggerMessage}</div>
     655                <div class="bcx-widget__ping-message-text">{this.state.selectedTriggerMessage.message}</div>
    619656                <div class="bcx-widget__ping-status">
    620657                  <span class="bcx-widget__ping-status-dot"></span>
     
    622659                </div>
    623660              </div>
    624               <button class="bcx-widget__ping-close" onClick={this.handlePingMessageClose} aria-label="Close ping message" data-adblock-bypass="true">
     661              <button
     662                class="bcx-widget__ping-close"
     663                onClick={e => {
     664                  e.preventDefault();
     665                  e.stopPropagation();
     666                  this.handlePingMessageClose();
     667                }}
     668                onTouchEnd={e => {
     669                  e.preventDefault();
     670                  e.stopPropagation();
     671                  this.handlePingMessageClose();
     672                }}
     673                aria-label="Close ping message"
     674                data-adblock-bypass="true"
     675                type="button"
     676              >
    625677                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    626678                  <line x1="18" y1="6" x2="6" y2="18"></line>
     
    897949  }
    898950
    899   private startPingMessageTimer() {
     951  /**
     952   * Select the appropriate trigger message based on current page URL
     953   * Matches URL patterns and falls back to default message only if origin matches
     954   */
     955  private selectTriggerMessage(
     956    triggerMessages: Array<{
     957      id: number;
     958      url: string;
     959      message: string;
     960      is_default: boolean;
     961      trigger_after_seconds: number;
     962      created_at: string;
     963      updated_at: string;
     964    }>,
     965  ):
     966    | {
     967        id: number;
     968        url: string;
     969        message: string;
     970        is_default: boolean;
     971        trigger_after_seconds: number;
     972        created_at: string;
     973        updated_at: string;
     974      }
     975    | undefined {
     976    if (!triggerMessages || triggerMessages.length === 0) {
     977      return undefined;
     978    }
     979
     980    const currentPath = window.location.pathname;
     981    const currentOrigin = window.location.origin;
     982
     983    // Check if current origin matches any trigger message origin
     984    let originMatches = false;
     985    const defaultMessage = triggerMessages.find(msg => msg.is_default);
     986
     987    // First, try to find a match for the current full URL (exact match)
     988    for (const triggerMessage of triggerMessages) {
     989      if (triggerMessage.is_default) {
     990        continue; // Skip defaults in first pass
     991      }
     992
     993      try {
     994        const triggerUrl = new URL(triggerMessage.url);
     995        const triggerOrigin = triggerUrl.origin;
     996        const triggerPath = triggerUrl.pathname;
     997
     998        // Track if origin matches (for default fallback check)
     999        if (triggerOrigin === currentOrigin) {
     1000          originMatches = true;
     1001
     1002          // If trigger path is just "/" or empty, it matches homepage
     1003          if (triggerPath === '/' || triggerPath === '') {
     1004            if (currentPath === '/' || currentPath === '') {
     1005              return triggerMessage;
     1006            }
     1007          } else {
     1008            // Check if current path starts with trigger path (for subpage matching like "/cart" matching "/cart/checkout")
     1009            if (currentPath === triggerPath || currentPath.startsWith(triggerPath + '/')) {
     1010              return triggerMessage;
     1011            }
     1012          }
     1013        }
     1014      } catch {
     1015        // Invalid URL format, skip this trigger message
     1016        continue;
     1017      }
     1018    }
     1019
     1020    // Check if default message origin matches current origin
     1021    if (defaultMessage) {
     1022      try {
     1023        const defaultUrl = new URL(defaultMessage.url);
     1024        const defaultOrigin = defaultUrl.origin;
     1025        if (defaultOrigin === currentOrigin) {
     1026          originMatches = true;
     1027        }
     1028      } catch {
     1029        // Invalid URL format, skip
     1030      }
     1031    }
     1032
     1033    // If no exact match found but origin matches, return the default message
     1034    // If origin doesn't match (different website), return undefined
     1035    if (originMatches && defaultMessage) {
     1036      return defaultMessage;
     1037    }
     1038
     1039    return undefined;
     1040  }
     1041
     1042  private startPingMessageTimer(triggerMessage: {
     1043    id: number;
     1044    url: string;
     1045    message: string;
     1046    is_default: boolean;
     1047    trigger_after_seconds: number;
     1048    created_at: string;
     1049    updated_at: string;
     1050  }) {
    9001051    // Clear any existing timer
    9011052    this.clearPingMessageTimer();
    9021053
    903     // Set timer for 15 seconds
     1054    // Use trigger_after_seconds from the message (convert to milliseconds)
     1055    const delayMs = triggerMessage.trigger_after_seconds * 1000;
     1056
    9041057    this.pingMessageTimeout = setTimeout(() => {
    9051058      // Only show ping message if chat is not open and not already shown
    906       if (!this.state.isOpen && !this.state.showPingMessage && this.state.triggerMessage) {
     1059      if (!this.state.isOpen && !this.state.showPingMessage && this.state.selectedTriggerMessage) {
    9071060        this.setState({ showPingMessage: true });
    9081061      }
    909     }, 12500);
     1062    }, delayMs);
    9101063  }
    9111064
  • bettercx-widget/trunk/src/types/api.ts

    r3385343 r3402632  
    146146  logo?: string;
    147147  welcomeMessage?: string;
    148   triggerMessage?: string; // Added for the ping message feature
     148  triggerMessages?: Array<{
     149    id: number;
     150    url: string;
     151    message: string;
     152    is_default: boolean;
     153    trigger_after_seconds: number;
     154    created_at: string;
     155    updated_at: string;
     156  }>; // Array of trigger messages from backend
     157  selectedTriggerMessage?: {
     158    id: number;
     159    url: string;
     160    message: string;
     161    is_default: boolean;
     162    trigger_after_seconds: number;
     163    created_at: string;
     164    updated_at: string;
     165  }; // The trigger message selected based on URL matching
    149166  showPingMessage?: boolean; // Added to control ping message visibility
    150167  agentName?: string; // Added for custom agent name from backend
Note: See TracChangeset for help on using the changeset viewer.