@xapp/chat-widget
Version:
XAPP Chat Widget
1,181 lines (1,123 loc) • 1.33 MB
JavaScript
'use strict';
var require$$1 = require('react');
var require$$0 = require('react/jsx-runtime');
var reactRedux = require('react-redux');
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var lib = {};
var ActionBar$1 = {};
/*! Copyright (c) 2021, XAPPmedia */
Object.defineProperty(ActionBar$1, "__esModule", { value: true });
ActionBar$1.isActionBarChatButton = isActionBarChatButton;
ActionBar$1.isActionBarFormButton = isActionBarFormButton;
ActionBar$1.isActionBarScheduleButton = isActionBarScheduleButton;
ActionBar$1.isActionBarPhoneButton = isActionBarPhoneButton;
ActionBar$1.isActionBarUrlButton = isActionBarUrlButton;
ActionBar$1.isActionBarEmailButton = isActionBarEmailButton;
ActionBar$1.isActionBarHelpButton = isActionBarHelpButton;
ActionBar$1.isActionBarPaymentButton = isActionBarPaymentButton;
/**
* Type guard for ActionBarChatButton
*/
function isActionBarChatButton(button) {
return button.type === "chat";
}
/**
* Type guard for ActionBarFormButton
*/
function isActionBarFormButton(button) {
return button.type === "form";
}
/**
* Type guard for ActionBarScheduleButton
*/
function isActionBarScheduleButton(button) {
return button.type === "schedule";
}
/**
* Type guard for ActionBarPhoneButton
*/
function isActionBarPhoneButton(button) {
return button.type === "phone";
}
/**
* Type guard for ActionBarUrlButton
*/
function isActionBarUrlButton(button) {
return button.type === "url";
}
/**
* Type guard for ActionBarEmailButton
*/
function isActionBarEmailButton(button) {
return button.type === "email";
}
/**
* Type guard for ActionBarHelpButton
*/
function isActionBarHelpButton(button) {
return button.type === "help";
}
/**
* Type guard for ActionBarPaymentButton
*/
function isActionBarPaymentButton(button) {
return button.type === "payment";
}
var ChannelData = {};
Object.defineProperty(ChannelData, "__esModule", { value: true });
var Constants = {};
Object.defineProperty(Constants, "__esModule", { value: true });
Constants.CHAT_WIDGET_CHANNEL = void 0;
Constants.CHAT_WIDGET_CHANNEL = "chat-widget";
var Types = {};
/*! Copyright (c) 2021, XAPPmedia */
Object.defineProperty(Types, "__esModule", { value: true });
var UserInfo = {};
Object.defineProperty(UserInfo, "__esModule", { value: true });
var WidgetEnv = {};
Object.defineProperty(WidgetEnv, "__esModule", { value: true });
var WidgetTheme = {};
Object.defineProperty(WidgetTheme, "__esModule", { value: true });
var guards = {};
Object.defineProperty(guards, "__esModule", { value: true });
guards.isStaticImageMenuItem = isStaticImageMenuItem;
guards.isStaticTextMenuItem = isStaticTextMenuItem;
guards.isStandardMenuItem = isStandardMenuItem;
guards.isOpenURLMenuItem = isOpenURLMenuItem;
function isStaticImageMenuItem(item) {
return !!item && !!item.imageUrl;
}
function isStaticTextMenuItem(item) {
return !!item && !!item.body;
}
function isStandardMenuItem(item) {
return !!item && !!item.label;
}
function isOpenURLMenuItem(item) {
return !!item && !!item.url;
}
(function (exports$1) {
var __createBinding = (commonjsGlobal && commonjsGlobal.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (commonjsGlobal && commonjsGlobal.__exportStar) || function(m, exports$1) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
};
Object.defineProperty(exports$1, "__esModule", { value: true });
/*! Copyright (c) 2021, XAPPmedia */
__exportStar(ActionBar$1, exports$1);
__exportStar(ChannelData, exports$1);
__exportStar(Constants, exports$1);
__exportStar(Types, exports$1);
__exportStar(UserInfo, exports$1);
__exportStar(WidgetEnv, exports$1);
__exportStar(WidgetTheme, exports$1);
__exportStar(guards, exports$1);
} (lib));
var ChatIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })); };
var FormIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6zm2-6h8v2H8v-2zm0-3h8v2H8v-2zm0 6h5v2H8v-2z" }) })); };
var ScheduleIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zM9 14H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm-8 4H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2z" }) })); };
var PhoneIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) })); };
var LinkIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" }) })); };
var EmailIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" }) })); };
var HelpIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z" }) })); };
var PaymentIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z" }) })); };
// Default fallback icon for unsupported types
var DefaultIcon = function () { return (require$$0.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: require$$0.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" }) })); };
var iconComponents = {
chat: ChatIcon,
form: FormIcon,
calendar: ScheduleIcon, // GraphQL schema uses "calendar"
schedule: ScheduleIcon, // Keep "schedule" for backwards compatibility
phone: PhoneIcon,
link: LinkIcon, // GraphQL schema uses "link"
url: LinkIcon, // Keep "url" for backwards compatibility
email: EmailIcon,
help: HelpIcon,
payment: PaymentIcon,
// "custom" type is handled via iconUrl, no need for a component
};
var ActionBarIcon = function (_a) {
var type = _a.type, icon = _a.icon, iconUrl = _a.iconUrl, className = _a.className;
if (iconUrl) {
return (require$$0.jsx("div", { className: className, children: require$$0.jsx("img", { src: iconUrl, alt: "".concat(icon || type, " icon") }) }));
}
// Use explicit icon override if provided, otherwise fall back to type
var iconType = icon || type;
var IconComponent = iconComponents[iconType] || DefaultIcon;
return (require$$0.jsx("div", { className: className, children: require$$0.jsx(IconComponent, {}) }));
};
/**
* Allowed URL protocols for security. Prevents javascript: and other potentially dangerous protocols.
*/
var ALLOWED_PROTOCOLS = ["http:", "https:", "tel:", "mailto:"];
/**
* Validates a URL to ensure it uses an allowed protocol.
* Returns true if the URL is safe to navigate to.
*/
function isValidUrl(url) {
try {
var parsed = new URL(url, window.location.href);
return ALLOWED_PROTOCOLS.includes(parsed.protocol);
}
catch (_a) {
// Invalid URL
return false;
}
}
/**
* Safely navigates to a URL after validation.
* Returns false if the URL was invalid and navigation was blocked.
*/
function safeNavigate(url, target, windowOptions) {
if (!isValidUrl(url)) {
console.warn("[ActionBar] Blocked navigation to unsafe URL:", url);
return false;
}
if (target === "sameWindow") {
window.location.href = url;
}
else if (target === "newWindow" && windowOptions) {
var features = "width=".concat(windowOptions.width || 600, ",height=").concat(windowOptions.height || 400);
window.open(url, "_blank", features);
}
else {
// newTab (default)
window.open(url, "_blank", "noopener,noreferrer");
}
return true;
}
var defaultLabels = {
chat: "Chat",
form: "Contact",
schedule: "Book",
phone: "Call",
url: "Link",
email: "Email",
help: "Help",
payment: "Pay",
};
var ActionBarButton = function (_a) {
var _b;
var button = _a.button, chatActive = _a.chatActive, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick, isMobile = _a.isMobile;
var handleClick = require$$1.useCallback(function () {
if (lib.isActionBarChatButton(button)) {
// Toggle chat: if active, minimize; if not active, open
if (chatActive) {
onChatMinimize();
}
else {
onChatClick();
}
}
else if (lib.isActionBarFormButton(button) || lib.isActionBarScheduleButton(button)) {
// Both form and schedule buttons open the form widget modal
onFormClick(button);
}
else if (lib.isActionBarPhoneButton(button)) {
// Phone: Use tel: link
var phoneNumber = button.phoneNumber.replace(/\s/g, "");
window.location.href = "tel:".concat(phoneNumber);
}
else if (lib.isActionBarUrlButton(button)) {
// URL: Open according to target (with validation)
var url = button.url, _a = button.target, target = _a === void 0 ? "newTab" : _a, windowOptions = button.windowOptions;
safeNavigate(url, target, windowOptions);
}
else if (lib.isActionBarEmailButton(button)) {
// Email: Build mailto: link with optional subject and body
var emailAddress = button.emailAddress, subject = button.subject, body = button.body;
var mailto = "mailto:".concat(emailAddress);
var params = [];
if (subject) {
params.push("subject=".concat(encodeURIComponent(subject)));
}
if (body) {
params.push("body=".concat(encodeURIComponent(body)));
}
if (params.length > 0) {
mailto += "?".concat(params.join("&"));
}
window.location.href = mailto;
}
else if (lib.isActionBarHelpButton(button)) {
// Help: Open URL according to target (with validation)
var url = button.url, _b = button.target, target = _b === void 0 ? "newTab" : _b;
safeNavigate(url, target);
}
else if (lib.isActionBarPaymentButton(button)) {
// Payment: Open URL according to target (with validation)
var url = button.url, _c = button.target, target = _c === void 0 ? "newTab" : _c;
safeNavigate(url, target);
}
}, [button, chatActive, onChatClick, onChatMinimize, onFormClick]);
// Handle visibility
var visibility = button.visibility || "all";
if (visibility === "mobile-only" && !isMobile) {
return null;
}
if (visibility === "desktop-only" && isMobile) {
return null;
}
var ariaLabel = button.label || defaultLabels[button.type] || button.type;
var tabIndex = (_b = button.tabIndex) !== null && _b !== void 0 ? _b : 0;
// Chat button shows active state when chat is open
var isActive = lib.isActionBarChatButton(button) && chatActive;
var buttonClassName = [
"xapp-action-bar__button",
isActive ? "xapp-action-bar__button--active" : "",
].filter(Boolean).join(" ");
return (require$$0.jsxs("button", { className: buttonClassName, onClick: handleClick, "aria-label": ariaLabel, "aria-pressed": isActive, tabIndex: tabIndex, children: [require$$0.jsx(ActionBarIcon, { type: button.type, icon: button.icon, iconUrl: button.iconUrl, className: "xapp-action-bar__icon" }), button.label && (require$$0.jsx("span", { className: "xapp-action-bar__label", children: button.label }))] }));
};
var DEFAULT_MOBILE_BREAKPOINT = 768;
var RESIZE_DEBOUNCE_MS = 150;
var ActionBar = function (_a) {
var _b, _c;
var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick;
var _d = require$$1.useState(false), isMobile = _d[0], setIsMobile = _d[1];
var mobileBreakpoint = (_b = config.mobileBreakpoint) !== null && _b !== void 0 ? _b : DEFAULT_MOBILE_BREAKPOINT;
var position = (_c = config.position) !== null && _c !== void 0 ? _c : "right";
var resizeTimeoutRef = require$$1.useRef(null);
// Debounced resize handler
var handleResize = require$$1.useCallback(function () {
if (resizeTimeoutRef.current) {
window.clearTimeout(resizeTimeoutRef.current);
}
resizeTimeoutRef.current = window.setTimeout(function () {
setIsMobile(window.innerWidth <= mobileBreakpoint);
}, RESIZE_DEBOUNCE_MS);
}, [mobileBreakpoint]);
require$$1.useEffect(function () {
// Initial check (immediate, not debounced)
setIsMobile(window.innerWidth <= mobileBreakpoint);
window.addEventListener("resize", handleResize);
return function () {
window.removeEventListener("resize", handleResize);
if (resizeTimeoutRef.current) {
window.clearTimeout(resizeTimeoutRef.current);
}
};
}, [mobileBreakpoint, handleResize]);
// Filter out chat buttons if chat is disabled
var filteredButtons = chatDisabled
? config.buttons.filter(function (button) { return !lib.isActionBarChatButton(button); })
: config.buttons;
// Build class names - ActionBar stays visible even when chat is open
var classNames = [
"xapp-action-bar",
"xapp-action-bar--".concat(position),
isMobile ? "xapp-action-bar--mobile" : "xapp-action-bar--desktop",
]
.filter(Boolean)
.join(" ");
return (require$$0.jsx("div", { className: classNames, role: "toolbar", "aria-label": "Chat actions", children: filteredButtons.map(function (button) { return (require$$0.jsx(ActionBarButton, { button: button, chatActive: visible, onChatClick: onChatClick, onChatMinimize: onChatMinimize, onFormClick: onFormClick, isMobile: isMobile }, button.id)); }) }));
};
/**
* ActionBarPreview is a simplified version of ActionBar designed for use in Studio
* configuration UI. It renders a visual preview of the action bar without requiring
* interactive callbacks.
*
* This component provides no-op handlers for all button interactions, making it
* suitable for configuration previews where you want to show how the action bar
* will look without actual functionality.
*
* @example
* ```tsx
* <ActionBarPreview
* config={{
* enabled: true,
* buttons: [
* { id: '1', type: 'chat', label: 'Chat', icon: 'chat' },
* { id: '2', type: 'phone', label: 'Call', icon: 'phone', phoneNumber: '555-1234' }
* ],
* position: 'right'
* }}
* />
* ```
*/
var ActionBarPreview = function (_a) {
var config = _a.config, _b = _a.chatActive, chatActive = _b === void 0 ? false : _b;
// No-op handlers for preview mode
var handleChatClick = require$$1.useCallback(function () {
// Preview only - no action
}, []);
var handleChatMinimize = require$$1.useCallback(function () {
// Preview only - no action
}, []);
var handleFormClick = require$$1.useCallback(function (_button) {
// Preview only - no action
}, []);
return (require$$0.jsx(ActionBar, { config: config, visible: chatActive, chatDisabled: false, onChatClick: handleChatClick, onChatMinimize: handleChatMinimize, onFormClick: handleFormClick }));
};
var ActionButton = function (_a) {
var label = _a.label, disable = _a.disable, type = _a.type, addClass = _a.addClass, onClick = _a.onClick;
function handleClick(ev) {
ev.stopPropagation();
if (onClick) {
onClick(label);
}
}
return (require$$0.jsx("button", { disabled: disable, type: type, className: "action-button ".concat(addClass), onClick: handleClick, children: label }));
};
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter$1(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator$1(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __spreadArray$1(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/*! Copyright (c) 2021, XAPPmedia */
function insertSorted(array, obj, comparer) {
if (!array.length) {
array.push(obj);
return array;
}
// Handle single-element array case
if (array.length === 1) {
var comp = comparer(obj, array[0]);
if (comp === 0) {
if (obj === array[0]) {
return array; // Same object reference, skip duplicate
}
// Same comparison value but different object - insert after
array.push(obj);
return array;
}
if (comp < 0) {
array.unshift(obj);
}
else {
array.push(obj);
}
return array;
}
var leftIndex = 0;
var rightIndex = array.length - 1;
while ((rightIndex - leftIndex) > 0) {
var left = array[leftIndex];
var right = array[rightIndex];
var compLeft = comparer(obj, left);
// Allow items with equal comparison values (e.g., same timestamp) to be inserted.
// Only skip if it's the exact same object reference.
if (compLeft === 0) {
if (obj === left) {
return array; // Same object reference, skip duplicate
}
// Same comparison value but different object - insert after this position
array.splice(leftIndex + 1, 0, obj);
return array;
}
if (compLeft < 0) {
array.splice(leftIndex, 0, obj);
return array;
}
var compRight = comparer(obj, right);
if (compRight === 0) {
if (obj === right) {
return array; // Same object reference, skip duplicate
}
// Same comparison value but different object - insert after this position
array.splice(rightIndex + 1, 0, obj);
return array;
}
if (compRight > 0) {
array.splice(rightIndex + 1, 0, obj);
return array;
}
if (rightIndex - leftIndex <= 1) {
break;
}
var centerIndex = Math.floor((leftIndex + rightIndex) / 2);
var center = array[centerIndex];
var comp = comparer(center, obj);
// Check for same object reference at center
if (comp === 0 && obj === center) {
return array; // Same object reference, skip duplicate
}
if (comp <= 0) {
leftIndex = centerIndex;
continue;
}
if (comp > 0) {
rightIndex = centerIndex;
continue;
}
}
array.splice(leftIndex + 1, 0, obj);
return array;
}
/**
* Agent is determined if the provided nick string starts with `agent:`.
*
* This returns false if the nick string is falsey (undefined or "") or does not start with "agent:"
* @param nick
* @returns
*/
function isAgent(nick) {
if (!nick) {
return false;
}
return nick.startsWith("agent:");
}
function useIsMounted() {
var ref = require$$1.useRef(false);
require$$1.useEffect(function () {
ref.current = true;
return function () {
ref.current = false;
};
}, []);
return require$$1.useCallback(function () { return ref.current; }, []);
}
var RetryOptions = /** @class */ (function () {
function RetryOptions() {
this.retries = 5;
this.timeout = 1000;
this.log = false;
}
return RetryOptions;
}());
var RetryRequest = /** @class */ (function () {
function RetryRequest(options) {
if (options) {
this.options = Object.assign(new RetryOptions(), options);
}
}
/**
* Fetch with retry and timeout
*
* Help from https://stackoverflow.com/a/66499718
* @param executor
* @param options
* @returns
*/
RetryRequest.prototype.fetch = function (executor, options) {
return __awaiter$1(this, void 0, void 0, function () {
var controller_1, timeoutId, response, e_1;
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0:
if (!options) {
options = this.options;
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 5]);
controller_1 = new AbortController();
timeoutId = setTimeout(function () {
controller_1.abort();
}, options.timeout);
return [4 /*yield*/, executor(controller_1.signal)];
case 2:
response = _a.sent();
clearTimeout(timeoutId);
return [2 /*return*/, response];
case 3:
e_1 = _a.sent();
if (options.retries === 1 || options.retries < 1) {
// Failed
throw (e_1);
}
return [4 /*yield*/, this.fetch(executor, {
timeout: options.timeout,
retries: options.retries - 1
})];
case 4:
// decrement the retries
return [2 /*return*/, _a.sent()];
case 5: return [2 /*return*/];
}
});
});
};
return RetryRequest;
}());
/**
* Get a display date of a specific point in time based on the current time.
*
* @param date
* @returns
*/
function getTimeAgo(date) {
var now = new Date();
var diffInSeconds = Math.floor((now.valueOf() - date.valueOf()) / 1000);
if (diffInSeconds < 1) {
return "now";
}
if (diffInSeconds < 60) {
return "".concat(diffInSeconds, " seconds ago");
}
var diffInMinutes = Math.floor(diffInSeconds / 60);
if (diffInMinutes < 60) {
return "".concat(diffInMinutes, " minutes ago");
}
var diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) {
return "".concat(diffInHours, " hours ago");
}
// If more than 24 hours ago, return the full date and time
return date.toLocaleString();
}
/*! Copyright (c) 2024, XAPPmedia */
var FORM_WIDGET_SCRIPT_ID = "xapp-form-js";
var FORM_WIDGET_BASE_URL = "https://form.xapp.ai/xapp-form-widget.js";
/** Default retry configuration */
var DEFAULT_MAX_RETRIES = 3;
var DEFAULT_RETRY_DELAY_MS = 1000;
/**
* Builds the form-widget script URL with the provided key
*/
function getFormWidgetScriptUrl(key) {
return "".concat(FORM_WIDGET_BASE_URL, "?key=").concat(encodeURIComponent(key));
}
/**
* Checks if the form-widget script is already loaded on the page.
* Checks both by ID and by src URL to handle various installation methods.
*/
function isFormWidgetScriptLoaded(key) {
// Check by standard form-widget ID
if (document.getElementById(FORM_WIDGET_SCRIPT_ID)) {
return true;
}
// Check by src URL if key is provided
if (key) {
var scriptUrl = getFormWidgetScriptUrl(key);
var scripts = document.querySelectorAll("script[src]");
for (var i = 0; i < scripts.length; i++) {
var src = scripts[i].getAttribute("src");
if (src && (src === scriptUrl || src.startsWith(FORM_WIDGET_BASE_URL))) {
return true;
}
}
}
return false;
}
/**
* Removes any failed script elements to allow retry
*/
function removeFailedScript() {
var existingScript = document.getElementById(FORM_WIDGET_SCRIPT_ID);
if (existingScript) {
existingScript.remove();
}
}
/**
* Delays execution for a specified number of milliseconds
*/
function delay(ms) {
return new Promise(function (resolve) { return setTimeout(resolve, ms); });
}
/**
* Attempts to load the script once
*/
function attemptScriptLoad(key) {
return new Promise(function (resolve, reject) {
var script = document.createElement("script");
script.id = FORM_WIDGET_SCRIPT_ID;
script.src = getFormWidgetScriptUrl(key);
script.async = true;
script.onload = function () {
resolve();
};
script.onerror = function () {
reject(new Error("Failed to load form-widget script: ".concat(script.src)));
};
document.head.appendChild(script);
});
}
/**
* Injects the form-widget script into the page with retry logic.
* Returns a promise that resolves when the script is loaded.
* Does nothing if the script is already loaded.
*
* @param key The form widget key
* @param maxRetries Maximum number of retry attempts (default: 3)
* @param retryDelayMs Delay between retries in milliseconds (default: 1000)
*/
function loadFormWidgetScript(key_1) {
return __awaiter$1(this, arguments, void 0, function (key, maxRetries, retryDelayMs) {
var lastError, attempt, error_1;
if (maxRetries === void 0) { maxRetries = DEFAULT_MAX_RETRIES; }
if (retryDelayMs === void 0) { retryDelayMs = DEFAULT_RETRY_DELAY_MS; }
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0:
// Check if already loaded
if (isFormWidgetScriptLoaded(key)) {
return [2 /*return*/];
}
attempt = 0;
_a.label = 1;
case 1:
if (!(attempt <= maxRetries)) return [3 /*break*/, 8];
_a.label = 2;
case 2:
_a.trys.push([2, 6, , 7]);
if (!(attempt > 0)) return [3 /*break*/, 4];
removeFailedScript();
console.log("[chat-widget] Retrying form-widget script load (attempt ".concat(attempt + 1, "/").concat(maxRetries + 1, ")"));
return [4 /*yield*/, delay(retryDelayMs)];
case 3:
_a.sent();
_a.label = 4;
case 4: return [4 /*yield*/, attemptScriptLoad(key)];
case 5:
_a.sent();
return [2 /*return*/]; // Success
case 6:
error_1 = _a.sent();
lastError = error_1;
if (attempt === maxRetries) {
console.error("[chat-widget] Failed to load form-widget script after all retries:", error_1);
}
return [3 /*break*/, 7];
case 7:
attempt++;
return [3 /*break*/, 1];
case 8: throw lastError || new Error("Failed to load form-widget script");
}
});
});
}
/**
* Pre-loads the form-widget script if a key is provided.
* This is a fire-and-forget operation - errors are logged but not thrown.
*/
function preloadFormWidgetScript(key) {
if (!key) {
return;
}
loadFormWidgetScript(key).catch(function (error) {
console.warn("[chat-widget] Failed to preload form-widget script:", error);
});
}
/**
* Checks if the form-widget control API is available
*/
function isFormWidgetReady() {
var _a;
return typeof ((_a = window.xafwControl) === null || _a === void 0 ? void 0 : _a.openForm) === "function";
}
/**
* Opens the form widget if it's available.
* Returns true if the form was opened, false otherwise.
*
* @param options Optional configuration for opening the form
*/
function openFormWidget(options) {
if (isFormWidgetReady()) {
window.xafwControl.openForm(options);
return true;
}
return false;
}
/**
* Attempts to open the form widget, waiting for it to be ready if necessary.
* Useful when the script is still loading.
*
* @param options Optional configuration for opening the form
* @param maxAttempts Maximum number of attempts to check for readiness
* @param intervalMs Time between attempts in milliseconds
*/
function openFormWidgetWhenReady(options, maxAttempts, intervalMs) {
if (maxAttempts === void 0) { maxAttempts = 20; }
if (intervalMs === void 0) { intervalMs = 100; }
return new Promise(function (resolve) {
// Try immediately first
if (openFormWidget(options)) {
resolve(true);
return;
}
var attempts = 0;
var checkInterval = setInterval(function () {
attempts++;
if (openFormWidget(options)) {
clearInterval(checkInterval);
resolve(true);
return;
}
if (attempts >= maxAttempts) {
clearInterval(checkInterval);
console.warn("[chat-widget] Form widget not ready after waiting");
resolve(false);
}
}, intervalMs);
});
}
var EMAIL_REGEX = "^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$";
// Initialize log level from localStorage on module load
var STORAGE_KEY = 'xaLogLevel';
var storedLogLevel = localStorage.getItem(STORAGE_KEY);
// Define property with getter/setter to persist to localStorage
// Only define the property if it hasn't been defined yet
if (!Object.getOwnPropertyDescriptor(window, "xaLogLevel")) {
Object.defineProperty(window, "xaLogLevel", {
// Use a private property to avoid infinite recursion in the getter/setter
get: function () {
return this._xaLogLevel;
},
set: function (value) {
this._xaLogLevel = value;
if (value) {
localStorage.setItem(STORAGE_KEY, value);
}
else {
localStorage.removeItem(STORAGE_KEY);
}
},
configurable: true,
// enumerable: true makes property visible in Object.keys() and for...in loops
// This is useful for debugging to see window.xaLogLevel in console inspection
enumerable: true
});
}
// Restore from localStorage if available
// Note: We don't check !window.xaLogLevel because the getter returns undefined initially,
// making such a check redundant on fresh loads
if (storedLogLevel) {
window.xaLogLevel = storedLogLevel;
}
// Create a namespaced API on window.xapp for easier access
if (typeof window !== 'undefined') {
window.xapp = window.xapp || {};
window.xapp.setLogLevel = function (level) {
if (level === 'off') {
window.xaLogLevel = null;
console.log('Log level set to: off (logs disabled)');
}
else {
window.xaLogLevel = level;
console.log("Log level set to: ".concat(level));
}
};
window.xapp.getLogLevel = function () {
return window.xaLogLevel || 'off';
};
}
// Returns current timestamp
function getCurrentDateString() {
return (new Date()).toISOString() + " ::";
}
function log() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (window.xaLogLevel === "debug") {
console.log.apply(console, __spreadArray$1([getCurrentDateString()], args, false));
}
}
function err() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
console.error.apply(console, __spreadArray$1([getCurrentDateString()], args, false));
}
var logOnce = function (key) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
var map = {};
if (!map[key]) {
map[key] = true;
log.apply(void 0, args);
}
};
var defaultServerUrl = "";
var defaultWidgetButtonWidth = "48";
var defaultMobileWidgetButtonWidth = "30";
var defaultNonMobileScreenWidth = "400";
var scheduleWidgetUrl = "ScheduleButton";
var ChatConfigContext = require$$1.createContext(null);
function useWidgetEnv() {
var ctx = require$$1.useContext(ChatConfigContext);
return ctx === null || ctx === void 0 ? void 0 : ctx.env;
}
function getServerConfig(env) {
if (env === null || env === void 0 ? void 0 : env.connection) {
return env.connection;
}
return {
serverUrl: env === null || env === void 0 ? void 0 : env.serverUrl,
accountKey: env === null || env === void 0 ? void 0 : env.accountKey,
type: (env === null || env === void 0 ? void 0 : env.direct) ? "direct" : "websocket",
};
}
function useServerConfig(env, nonce) {
var connection = getServerConfig(env);
var accountKey = connection.accountKey, serverUrl = connection.serverUrl, type = connection.type, timeout = connection.timeout;
return require$$1.useMemo(function () { return ({
accountKey: accountKey,
serverUrl: serverUrl,
timeout: timeout,
type: type,
nonce: nonce,
}); }, [accountKey, serverUrl, timeout, type, nonce]);
}
function useConnectionInfo(env) {
var nonce = reactRedux.useSelector(function (state) { return state.connection.nonce; });
log("nonce from Redux: ".concat(nonce));
return useServerConfig(env, nonce);
}
var Avatar = function (props) {
var _a, _b;
var style = {};
var child;
var entity = props.entity;
var chatConfig = require$$1.useContext(ChatConfigContext);
var avatarPath = entity === null || entity === void 0 ? void 0 : entity.avatarPath;
if (!avatarPath) {
var avatarImage = GenerateAvatar({
initials: (entity === null || entity === void 0 ? void 0 : entity.displayName) ? entity.displayName.slice(0, 2) : "?",
backgroundColor: (_b = (_a = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _a === void 0 ? void 0 : _a.theme) === null || _b === void 0 ? void 0 : _b.primaryColor,
size: 36,
});
avatarPath = avatarImage.src;
}
var agentAva = entity && isAgent(entity.nick);
if (agentAva) {
style.backgroundImage = "url('".concat(avatarPath, "')");
style.backgroundColor = "none";
}
else {
child = getVisitorSvg();
}
var hasImage = !!style.backgroundImage || !!child;
return (require$$0.jsx("div", { className: "avatar ".concat(agentAva ? "avatar--agent" : "avatar--visitor", " ").concat(!hasImage ? "avatar--empty" : ""), style: style, title: entity ? entity.display_name : "Agent", children: child }));
};
/**
* Generates an SVG based on the
* @returns
*/
function getVisitorSvg() {
return (require$$0.jsx("svg", { width: "16", height: "19", viewBox: "0 0 16 19", style: { margin: "0 auto", display: "block" }, children: require$$0.jsx("path", { d: "M11.5 5c0-1.933-1.567-3.5-3.5-3.5S4.5 3.067 4.5 5 6.067 8.5 8 8.5s3.5-1.567 3.5-3.5zM3 5c0-2.76 2.24-5 5-5s5 2.24 5 5-2.24 5-5 5-5-2.24-5-5zM1.955 17.294c.21-.642.504-1.285.898-1.88C3.963 13.74 5.615 12.75 8 12.75c2.385 0 4.038.99 5.147 2.664.394.595.69 1.238.898 1.88.124.382.19.672.214.822.063.41.447.69.856.625.41-.063.69-.447.625-.856-.034-.225-.118-.59-.27-1.053-.247-.763-.598-1.527-1.073-2.244C13.024 12.51 10.917 11.25 8 11.25c-2.916 0-5.024 1.26-6.397 3.336-.475.717-.826 1.48-1.074 2.245-.152.463-.236.83-.27 1.054-.065.41.215.793.624.857.41.065.793-.215.857-.624.025-.15.09-.44.215-.822z", fill: "#FFF", fillRule: "evenodd" }) }));
}
function GenerateAvatar(props) {
var initials = (props === null || props === void 0 ? void 0 : props.initials) || "?";
var size = (props === null || props === void 0 ? void 0 : props.size) || 40;
var backgroundColor = (props === null || props === void 0 ? void 0 : props.backgroundColor) || "#007bff";
var textColor = (props === null || props === void 0 ? void 0 : props.textColor) || "#ffffff";
var image = require$$1.useMemo(function () {
return generateImage(initials, size, backgroundColor, textColor);
}, [initials, size, backgroundColor, textColor]);
return image;
}
function generateImage(initials, size, backgroundColor, textColor) {
var canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
var context = canvas.getContext("2d");
// Draw background
context.fillStyle = backgroundColor;
context.fillRect(0, 0, size, size);
// Draw text
context.fillStyle = textColor;
context.font = "".concat(size / 2, "px Arial");
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText(initials, size / 2, size / 2);
// Create image
var image = new Image();
image.src = canvas.toDataURL();
return image;
}
function isChatServerActionLink(arg) {
return arg.hasOwnProperty("label");
}
function getChatServerActionLinkLabel(arg) {
if (isChatServerActionLink(arg)) {
return arg.label;
}
return arg;
}
function getItemUrl(item) {
return item.url || item.imageActionUrl;
}
var useChatDispatch = function () { return reactRedux.useDispatch(); };
var OptionalLink = function (props) {
var chatDispatch = useChatDispatch();
var visuals = reactRedux.useSelector(function (state) { return state.visuals; });
var url = props.url, className = props.className, onOpen = props.onOpen;
var handleOpenUrl = require$$1.useCallback(function () {
if (onOpen) {
onOpen(url, chatDispatch, visuals);
}
}, [url, onOpen]);
if (url) {
return (require$$0.jsx("div", { onClick: handleOpenUrl, className: className, children: props.children }));
}
else {
return (require$$0.jsx("div", { onClick: props.onClick, className: className, children: props.children }));
}
};
var ActionItem = function (props) {
var _a;
var item = props.item, onButtonClick = props.onButtonClick, onExecute = props.onExecute;
var singleButton = ((_a = item.buttons) === null || _a === void 0 ? void 0 : _a.length) === 1;
var handleClick = require$$1.useCallback(function () {
var _a;
var btns = (_a = item.buttons) === null || _a === void 0 ? void 0 : _a.length;
// If there is only one button, it takes precedent
// and the whole thing is the button
if (btns === 1) {
onButtonClick(item.buttons[0]);
}
else {
onExecute(item.title, item.token);
}
}, [item, onExecute, onButtonClick]);
var className = "xappw-chat-action-item ".concat(singleButton ? "xappw-chat-action-item--action" : "", " ").concat(props.className);
return (require$$0.jsx(OptionalLink, { className: className, url: getItemUrl(item), onClick: handleClick, onOpen: props.onOpenUrl, children: props.children }));
};
var ChatImage = function (props) {
var cleanUrl = props.imageUrl.replace(/'/g, "%27");
var content = (require$$0.jsx("div", { className: "chat-card-img__content", style: { backgroundImage: "url(".concat(cleanUrl, ")") } }));
if (!props.imageActionUrl) {
return require$$0.jsx("div", { className: "chat-card-img", children: content });
}
else {
return (require$$0.jsx("a", { href: props.imageActionUrl, "aria-label": "read more", target: "_blank", rel: "noopener noreferrer", className: "chat-card-img", children: content }));
}
};
var ActionItemImage = function (props) {
var _a;
var item = props.item;
var singleButton = ((_a = item.buttons) === null || _a === void 0 ? void 0 : _a.length) === 1;
var itemUrl = getItemUrl(item);
if (item.imageUrl) {
var imageActionUrl = !singleButton ? item.imageActionUrl : null;
return (require$$0.jsx("div", { className: props.className, children: require$$0.jsx(ChatImage, { imageUrl: item.imageUrl, imageActionUrl: !itemUrl && imageActionUrl }) }));
}
else if (props.emptyImageVisible) {
return require$$0.jsx("div", { className: props.className });
}
return require$$0.jsx(require$$0.Fragment, {});
};
var ChatActionButtonInner = function (props) {
var button = props.button, onClick = props.onClick;
var handleButton = require$$1.useCallback(function () {
onClick(button);
}, [button, onClick]);
return require$$0.jsx(ActionButton, { onClick: handleButton, addClass: "button-card", label: button.label });
};
var ChatActionButton = require$$1.memo(ChatActionButtonInner);
var ChatActionButtonsInner = function (props) {
return (require$$0.jsx("div", { className: "buttons-container " + (props.className || ""), children: props.buttons.map(function (button, i) {
return require$$0.jsx(ChatActionButton, { button: button, onClick: props.onClick }, i);
}) }));
};
var ChatActionButtons = require$$1.memo(ChatActionButtonsInner);
var CarouselItem = function (props) {
var _a;
var item = props.item;
return (require$$0.jsx(ActionItem, { className: "chat-list-item-container", item: item, onButtonClick: props.onButtonClick, onExecute: props.onExecute, onOpenUrl: props.onOpenUrl, children: require$$0.jsxs("div", { className: "chat-list-item", children: [require$$0.jsx(ActionItemImage, { item: item, className: "chat-list-item__img", emptyImageVisible: props.emptyImageVisible }), item.title && (require$$0.jsx("div", { className: "chat-list-item__title", children: require$$0.jsx("span", { children: item.title }) })), item.subTitle && (require$$0.jsx("div", { className: "chat-list-item__subtitle", children: require$$0.jsx("span", { children: item.subTitle }) })), !!((_a = item.buttons) === null || _a === void 0 ? void 0 : _a.length) && (require$$0.jsx(ChatActionButtons, { className: "chat-list-item__actions", buttons: item.buttons, onClick: props.onButtonClick }))] }) }));
};
function getLeftArrowSvg() {
return (require$$0.jsx("svg", { viewBox: "-5 -18 10 36", children: require$$0.jsx("path", { d: "M 2.5 -15 L -2.5 0 L 2.5 15", stroke: "currentColor", strokeLinecap: "square", strokeWidth: "4px", fill: "none" }) }));
}
var ChevronLeft = function (props) {
return (require$$0.jsx("button", { onClick: props.onClick, className: "xa-chevron", children: getLeftArrowSvg() }));
};
function getRightArrowSvg() {
return (require$$0.jsx("svg", { viewBox: "-5 -18 10 36", children: require$$0.jsx("path", { d: "M -2.5 -15 L 2.5 0 L -2.5 15", stroke: "currentColor", strokeLinecap: "square", strokeWidth: "4px", fill: "none" }) }));
}
var ChevronRight = function (props) {
return (require$$0.jsx("button", { onClick: props.onClick, className: "xa-chevron", children: getRightArrowSvg() }));
};
var Carousel = function (props) {
var listRef = require$$1.useRef(null);
var _a = require$$1.useState(0), visibleIndex = _a[0], setSetVisibleIndex = _a[1];
function scrollMe(direction) {
var _a, _b;
var scrollArea = listRef.current;
if (scrollArea) {
var items = scrollArea.getElementsByClassName("xappw-carousel-items__item");