UNPKG

@xapp/chat-widget

Version:
1,228 lines (1,169 loc) 1.22 MB
'use strict'; var React$1 = require('react'); var reactRedux = require('react-redux'); 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 (React$1.createElement("button", { disabled: disable, type: type, className: "action-button ".concat(addClass), onClick: handleClick }, label)); }; var defaultServerUrl = ""; var defaultWidgetButtonWidth = "48"; var defaultMobileWidgetButtonWidth = "30"; var defaultNonMobileScreenWidth = "400"; var scheduleWidgetUrl = "ScheduleButton"; var ChatConfigContext = React$1.createContext(null); function useWidgetEnv() { var ctx = React$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 React$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; }); return useServerConfig(env, nonce); } /****************************************************************************** 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; } 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); if (compLeft === 0) { return array; // exist } if (compLeft < 0) { array.splice(leftIndex, 0, obj); return array; } var compRight = comparer(obj, right); if (compRight === 0) { return array; // exist } 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); 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 = React$1.useRef(false); React$1.useEffect(function () { ref.current = true; return function () { ref.current = false; }; }, []); return React$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(); } var EMAIL_REGEX = "^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$"; // 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 Avatar = function (props) { var _a, _b; var style = {}; var child; var entity = props.entity; var chatConfig = React$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 (React$1.createElement("div", { className: "avatar ".concat(agentAva ? "avatar--agent" : "avatar--visitor", " ").concat(!hasImage ? "avatar--empty" : ""), style: style, title: entity ? entity.display_name : "Agent" }, child)); }; /** * Generates an SVG based on the * @returns */ function getVisitorSvg() { return (React$1.createElement("svg", { width: "16", height: "19", viewBox: "0 0 16 19", style: { margin: "0 auto", display: "block" } }, React$1.createElement("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 = React$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; } function useChatDispatch() { 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 = React$1.useCallback(function () { if (onOpen) { onOpen(url, chatDispatch, visuals); } }, [url, onOpen]); if (url) { return (React$1.createElement("div", { onClick: handleOpenUrl, className: className }, props.children)); } else { return (React$1.createElement("div", { onClick: props.onClick, className: className }, 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 = React$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 (React$1.createElement(OptionalLink, { className: className, url: getItemUrl(item), onClick: handleClick, onOpen: props.onOpenUrl }, props.children)); }; var ChatImage = function (props) { var cleanUrl = props.imageUrl.replace(/'/g, "%27"); var content = React$1.createElement("div", { className: "chat-card-img__content", style: { backgroundImage: "url(".concat(cleanUrl, ")") } }); if (!props.imageActionUrl) { return (React$1.createElement("div", { className: "chat-card-img" }, content)); } else { return (React$1.createElement("a", { href: props.imageActionUrl, "aria-label": "read more", target: "_blank", rel: "noopener noreferrer", className: "chat-card-img" }, 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 (React$1.createElement("div", { className: props.className }, React$1.createElement(ChatImage, { imageUrl: item.imageUrl, imageActionUrl: !itemUrl && imageActionUrl }))); } else if (props.emptyImageVisible) { return (React$1.createElement("div", { className: props.className })); } return React$1.createElement(React$1.Fragment, null); }; var ChatActionButtonInner = function (props) { var button = props.button, onClick = props.onClick; var handleButton = React$1.useCallback(function () { onClick(button); }, [button, onClick]); return (React$1.createElement(ActionButton, { onClick: handleButton, addClass: "button-card", label: button.label })); }; var ChatActionButton = React$1.memo(ChatActionButtonInner); var ChatActionButtonsInner = function (props) { return (React$1.createElement("div", { className: "buttons-container " + (props.className || "") }, props.buttons.map(function (button, i) { return React$1.createElement(ChatActionButton, { button: button, onClick: props.onClick, key: i }); }))); }; var ChatActionButtons = React$1.memo(ChatActionButtonsInner); var CarouselItem = function (props) { var _a; var item = props.item; return (React$1.createElement(ActionItem, { className: "chat-list-item-container", item: item, onButtonClick: props.onButtonClick, onExecute: props.onExecute, onOpenUrl: props.onOpenUrl }, React$1.createElement("div", { className: "chat-list-item" }, React$1.createElement(ActionItemImage, { item: item, className: "chat-list-item__img", emptyImageVisible: props.emptyImageVisible }), item.title && React$1.createElement("div", { className: "chat-list-item__title" }, React$1.createElement("span", null, item.title)), item.subTitle && React$1.createElement("div", { className: "chat-list-item__subtitle" }, React$1.createElement("span", null, item.subTitle)), !!((_a = item.buttons) === null || _a === void 0 ? void 0 : _a.length) && React$1.createElement(ChatActionButtons, { className: "chat-list-item__actions", buttons: item.buttons, onClick: props.onButtonClick })))); }; function getLeftArrowSvg() { return (React$1.createElement("svg", { viewBox: "-5 -18 10 36" }, React$1.createElement("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 (React$1.createElement("button", { onClick: props.onClick, className: "xa-chevron" }, getLeftArrowSvg())); }; function getRightArrowSvg() { return (React$1.createElement("svg", { viewBox: "-5 -18 10 36" }, React$1.createElement("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 (React$1.createElement("button", { onClick: props.onClick, className: "xa-chevron" }, getRightArrowSvg())); }; var Carousel = function (props) { var listRef = React$1.useRef(null); var _a = React$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"); var noOfItems = items.length; var newVisibleIndex = visibleIndex; // check the boundaries if (direction === 1) { if (visibleIndex < noOfItems - 1) { newVisibleIndex++; } } else { if (visibleIndex > 0) { newVisibleIndex--; } } var leftOffset = 0; // tslint:disable-next-line:prefer-for-of for (var index = 0; index < items.length; index++) { if (index === newVisibleIndex) { break; } leftOffset += (_b = (_a = items.item(index)) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : 0; } scrollArea.scrollLeft = leftOffset; setSetVisibleIndex(newVisibleIndex); } } var hasOnlyOneItem = props.list.items.length === 1; var hasImage = props.list.items.some(function (item) { return item.imageUrl; }); var listItems = props.list.items.map(function (item, itemIndex) { return (React$1.createElement("div", { className: "xappw-carousel-items__item", key: "item-key-".concat(itemIndex) }, React$1.createElement(CarouselItem, { item: item, emptyImageVisible: hasImage, onExecute: props.onExecute, onButtonClick: props.onButtonClick, onOpenUrl: props.onOpenUrl }))); }); return (React$1.createElement("div", { className: "xappw-carousel" }, !hasOnlyOneItem && (React$1.createElement("div", { className: "xappw-carousel__prev" }, React$1.createElement(ChevronLeft, { onClick: function () { return scrollMe(-1); } }))), React$1.createElement("div", { ref: listRef, className: "xappw-carousel-items ".concat(hasOnlyOneItem ? "xappw-carousel-items--one-item" : "") }, listItems), !hasOnlyOneItem && (React$1.createElement("div", { className: "xappw-carousel__next" }, React$1.createElement(ChevronRight, { onClick: function () { return scrollMe(1); } }))))); }; function useDimensions() { var ref = React$1.useRef(null); var _a = React$1.useState(), dimensions = _a[0], setDimensions = _a[1]; React$1.useLayoutEffect(function () { var _a, _b; setDimensions((_b = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.toJSON()); }, []); return [ref, dimensions]; } function add$1(left, right) { return { x: left.x + right.x, y: left.y + right.y }; } function sub$1(left, right) { return { x: left.x - right.x, y: left.y - right.y }; } function mul(val, k) { return { x: val.x * k, y: val.y * k }; } function len(vec) { return Math.sqrt(vec.x * vec.x + vec.y * vec.y); } function resize(vec, length) { var currentLength = len(vec); if (!currentLength) { return vec; } return mul(vec, length / currentLength); } function rotateZ(vec, angle) { var rad = angle * Math.PI / 180; var ca = Math.cos(rad); var sa = Math.sin(rad); var resx = vec.x * ca + vec.y * sa; var resy = vec.y * ca - vec.x * sa; return { x: resx, y: resy }; } function intersectLineEllipsis(ellipsis, line) { var lineStartRel = sub$1(line.start, ellipsis.center); var sx = lineStartRel.x; var sy = lineStartRel.y; var kx = line.direction.x; var ky = line.direction.y; var rx = ellipsis.radius.x; var ry = ellipsis.radius.y; var rx2 = rx * rx; var ry2 = ry * ry; var a = (kx * kx / rx2 + ky * ky / ry2); var b = (2 * kx * sx / rx2 + 2 * ky * sy / ry2); var c = (sx * sx / rx2 + sy * sy / ry2 - 1); var d2 = b * b - 4 * a * c; if (d2 >= 0) { var d = Math.sqrt(d2); var t1 = (-b - d) / (2 * a); var t2 = (-b + d) / (2 * a); if (t1 > 0 && t2 > 0) { return add$1(line.start, mul(line.direction, Math.min(t1, t2))); } if (t1 > 0) { return add$1(line.start, mul(line.direction, t1)); } if (t2 > 0) { return add$1(line.start, mul(line.direction, t2)); } } return line.start; } function getTailSvgPath$1(props, viewport) { var baseAngleRad = props.direction * Math.PI / 180; var radius = { x: props.width / 2, y: props.height / 2 }; var center = { x: viewport.x / 2, y: viewport.y / 2 }; var intersectionRel = { x: radius.x * Math.cos(baseAngleRad), y: radius.y * Math.sin(baseAngleRad) }; var farRel = resize(intersectionRel, len(intersectionRel) + props.length); var far = add$1(farRel, center); var direction = sub$1(intersectionRel, farRel); var angle = props.angle; var leftDirection = rotateZ(direction, angle / 2); var rightDirection = rotateZ(direction, -angle / 2); var ellipsis = { center: center, radius: radius }; var left = intersectLineEllipsis(ellipsis, { start: far, direction: leftDirection }); var right = intersectLineEllipsis(ellipsis, { start: far, direction: rightDirection }); return "m ".concat(far.x, " ").concat(far.y, " L ").concat(left.x, " ").concat(left.y, " L ").concat(right.x, " ").concat(right.y, " z"); } var CtaBubbleTail = function (props) { var _a, _b; var _c = useDimensions(), ref = _c[0], rect = _c[1]; var viewPort = { x: (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0, y: (_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0 }; return React$1.createElement("div", { ref: ref, className: "cta-bubble__tail" }, React$1.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 ".concat(viewPort.x, " ").concat(viewPort.y) }, React$1.createElement("path", { d: getTailSvgPath$1(props, viewPort), fill: "currentColor" }))); }; var CtaBubble = function (props) { var _a, _b, _c, _d; var _e = useDimensions(), ref = _e[0], rect = _e[1]; var animate = props.animate || false; var animation = animate && props.buttonAnimation === "bounce" ? "bounce" : "none"; var handleDismiss = function (e) { e.stopPropagation(); if (props.onDismiss) { props.onDismiss(); } }; return (React$1.createElement("div", { ref: ref, style: { border: props.borderStyle ? 'solid' : 'none', borderWidth: ((_a = props.borderStyle) === null || _a === void 0 ? void 0 : _a.width) || '0px', borderColor: ((_b = props.borderStyle) === null || _b === void 0 ? void 0 : _b.color) || 'transparent', animation: "".concat(animation, " 1s infinite"), }, className: "cta-bubble", onClick: props.onClick }, React$1.createElement(CtaBubbleTail, { width: (_c = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _c !== void 0 ? _c : 0, height: (_d = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _d !== void 0 ? _d : 0, direction: 60, angle: 45, length: 16 }), React$1.createElement("div", { className: "cta-bubble__content" }, props.children), props.dismissible && (React$1.createElement("button", { className: "cta-bubble__dismiss", onClick: handleDismiss, "aria-label": "Dismiss" }, React$1.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, React$1.createElement("path", { d: "M11 1L1 11M1 1L11 11", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })))))); }; var CtaBubbleContainer = function (props) { var visible = props.visible, timeout = props.timeout, delay = props.delay, animate = props.animate, buttonAnimation = props.buttonAnimation, dismissible = props.dismissible, onClick = props.onClick, onDismiss = props.onDismiss, children = props.children; var startTime = React$1.useMemo(function () { return visible ? new Date().valueOf() : undefined; }, [visible]); var _a = React$1.useState(false), showBubble = _a[0], setShowBubble = _a[1]; var isMounted = React$1.useRef(true); React$1.useEffect(function () { var hideTimer = null; if (startTime) { var delayTimer_1 = setTimeout(function () { if (isMounted.current) { setShowBubble(true); } if (typeof timeout === "number") { hideTimer = setTimeout(function () { if (isMounted.current) { setShowBubble(false); } }, timeout); } }, delay); return function () { if (hideTimer) { clearTimeout(hideTimer); } clearTimeout(delayTimer_1); isMounted.current = false; }; } // Return a cleanup function for the case where startTime is undefined return function () { if (hideTimer) { clearTimeout(hideTimer); } isMounted.current = false; }; }, [startTime, timeout, delay]); var handleDismiss = function () { setShowBubble(false); if (onDismiss) { onDismiss(); } }; return (React$1.createElement(React$1.Fragment, null, visible && showBubble && (React$1.createElement(CtaBubble, { onClick: onClick, animate: animate, buttonAnimation: buttonAnimation, dismissible: dismissible, onDismiss: handleDismiss }, children)))); }; var ChatButton = function (_a) { var _b; var onClick = _a.onClick, addClass = _a.addClass, config = _a.config, visible = _a.visible, borderStyle = _a.borderStyle, imageUrl = _a.imageUrl; var _c = React$1.useState(defaultWidgetButtonWidth), buttonWidth = _c[0], setButtonWidth = _c[1]; var _d = React$1.useState(false), animate = _d[0], setAnimate = _d[1]; var mobileWidth = ((_b = config === null || config === void 0 ? void 0 : config.mobile) === null || _b === void 0 ? void 0 : _b.applyAtLessThanWidth) || defaultNonMobileScreenWidth; // Determines mobile or normal var getConfigToApply = function () { var screenWidth = window.innerWidth; if (screenWidth <= parseInt(mobileWidth, 10) && (config === null || config === void 0 ? void 0 : config.mobile)) { return config.mobile; } else { return config; } }; var configToApply = getConfigToApply(); React$1.useEffect(function () { var _a; var delayTimer; var timeoutTimer; // Use animationDelay if provided, otherwise fall back to delay for backwards compatibility var animationDelayToUse = (_a = configToApply === null || configToApply === void 0 ? void 0 : configToApply.animationDelay) !== null && _a !== void 0 ? _a : configToApply === null || configToApply === void 0 ? void 0 : configToApply.delay; if (animationDelayToUse) { // Set a delay for the animation to start delayTimer = setTimeout(function () { setAnimate(true); if (configToApply.animationTimeout) { // Stop the animation after the specified timeout timeoutTimer = setTimeout(function () { setAnimate(false); }, configToApply.animationTimeout); } }, animationDelayToUse); } else if (configToApply === null || configToApply === void 0 ? void 0 : configToApply.animationTimeout) { // Start animation immediately and set a timeout to stop it setAnimate(true); timeoutTimer = setTimeout(function () { setAnimate(false); }, configToApply.animationTimeout); } else if (configToApply === null || configToApply === void 0 ? void 0 : configToApply.animation) { // No delay or timeout, start animation immediately setAnimate(true); } return function () { // Clear timers on unmount or when dependencies change if (delayTimer) clearTimeout(delayTimer); if (timeoutTimer) clearTimeout(timeoutTimer); }; }, [configToApply, setAnimate]); var animation = animate ? (configToApply === null || configToApply === void 0 ? void 0 : configToApply.animation) || "wiggle" : "none"; React$1.useEffect(function () { var handleResize = function () { var screenWidth = window.innerWidth; var newButtonWidth = screenWidth < parseInt(mobileWidth, 10) ? defaultMobileWidgetButtonWidth : defaultWidgetButtonWidth; setButtonWidth(newButtonWidth); }; handleResize(); window.addEventListener("resize", handleResize); return function () { window.removeEventListener("resize", handleResize); }; }, [mobileWidth]); var maxSvgSize = 22; var svgSize = Math.min(maxSvgSize, (+buttonWidth / +defaultWidgetButtonWidth) * maxSvgSize); return (React$1.createElement("button", { "aria-label": "open chat", className: "xapp-chat-button ".concat(addClass || "").trim(), onClick: onClick }, React$1.createElement("div", { id: "xapp-widget-button", className: "xapp-chat-button__btn", style: { width: "".concat(buttonWidth, "px"), height: "".concat(buttonWidth, "px"), borderRadius: "".concat(+buttonWidth / 2, "px"), border: borderStyle && borderStyle.width ? "solid" : "none", borderWidth: (borderStyle === null || borderStyle === void 0 ? void 0 : borderStyle.width) || "0px", borderColor: (borderStyle === null || borderStyle === void 0 ? void 0 : borderStyle.color) || "transparent", animation: "".concat(animation, " 1s infinite"), } }, imageUrl ? ( // Display image from URL React$1.createElement("img", { src: imageUrl, alt: "Chat Icon" })) : ( // Fallback to default SVG React$1.createElement("svg", { width: svgSize, height: svgSize, viewBox: "0 0 22 22" }, React$1.createElement("path", { d: "M13 22l-4-6H2c-1.11-.043-2-.935-2-2V2C0 .89.89 0 2 0h18c1.11 0 2 .892 2 2v12c0 1.067-.89 1.957-2 2h-3l-4 6zm3-8h4c-.005.3-.01-12 0-12-.01.004-18 .006-18 0 .005.006 0 12 0 12h8l3 5 3-5z", fill: "#FFF", fillRule: "evenodd" })))), configToApply && configToApply.message && (React$1.createElement("div", { className: "xapp-chat-button__cta" }, React$1.createElement(CtaBubbleContainer, { timeout: configToApply === null || configToApply === void 0 ? void 0 : configToApply.timeout, delay: configToApply === null || configToApply === void 0 ? void 0 : configToApply.delay, animate: animate, buttonAnimation: animation, dismissible: configToApply === null || configToApply === void 0 ? void 0 : configToApply.dismissible, visible: !visible /** Why is this !visible */ }, configToApply === null || configToApply === void 0 ? void 0 : configToApply.message))))); }; var ChatCard = function (props) { var _a; var card = props.card; var title; var image; var content; if (card.imageUrl) { image = React$1.createElement("div", { className: "chat-card-container__img" }, React$1.createElement(ChatImage, { imageUrl: card.imageUrl, imageActionUrl: card.imageActionUrl })); } if (card.title) { title = React$1.createElement("div", { className: "chat-card-title" }, React$1.createElement("span", null, card.title)); } if (card.content) { content = React$1.createElement("div", { className: "chat-card-sub-title" }, React$1.createElement("span", null, card.content)); } return (React$1.createElement("div", { className: "chat-card-container" }, image, title, content, !!((_a = card.buttons) === null || _a === void 0 ? void 0 : _a.length) && React$1.createElement(ChatActionButtons, { buttons: card.buttons, onClick: props.onButtonClick }))); }; var ChatChip = function (props) { var option = props.option; var className = option.actionUrl ? "chat-chip chat-chip-link" : "chat-chip"; return (React$1.createElement("div", { onClick: function () { return props === null || props === void 0 ? void 0 : props.onClick(option); }, className: className }, React$1.createElement("span", null, typeof option === "object" ? option.label : option))); }; /** * Render option list. We just follow the order. */ var ChatChips = function (props) { return (React$1.createElement("div", { className: "chat-chips" }, props.options.map(function (option, i) { return React$1.createElement(ChatChip, { key: i, onClick: props.onOptionClick, option: option }); }))); }; var defaultBehavior = { type: "newWindow" }; function resolveUrlPolicy(url, config) { if (config.policies) { for (var _i = 0, _a = config.policies; _i < _a.length; _i++) { var policy = _a[_i]; try { var re = new RegExp(policy.pattern, "gi"); if (re.test(url)) { return policy.behavior; } } catch (_b) { } } } return config.defaultBehavior || defaultBehavior; } // compile time full switch-case verification function throwBadKind$1(x) { throw new Error("Unknown kind " + x); } function setVisualStatus(status) { return { type: "visual_status", detail: { status: status, timestamp: new Date().getTime() } }; } function execute(url, dispatch, visuals, behavior) { var _a, _b; if (!dispatch || !visuals) { throw new Error("OpenUrl: No dispatch and/or visual."); } if (url === scheduleWidgetUrl) { // It's actually toggle var toggleForm = (_a = window.xafwControl) === null || _a === void 0 ? void 0 : _a.openForm; if (!toggleForm) { err("Cannot find toggleForm! Update form widget!"); return; } var isFormActive = false; var formStatus = (_b = window.xafwControl) === null || _b === void 0 ? void 0 : _b.formStatus; if (formStatus) { var formVisuals = formStatus(); isFormActive = formVisuals.active; } else { err("Cannot find formStatus! Update form widget!"); } // If form is active and we are here, then both widgets are active. Do nothing. Let the user X out the widgets. if (!isFormActive) { toggleForm(); if (visuals.opened) { dispatch(setVisualStatus({ opened: false })); } } return; } // Attempt to add query string to the url // if the query string already exists, it will not be added try { var urlObj = new URL(url); var params = new URLSearchParams(urlObj.search); // look for utm_source, if it doesn't exist, add it if (!params.has("utm_source")) { params.append("utm_source", "xapp"); } // now add the query string back to the url urlObj.search = params.toString(); // and finally, update the url url = urlObj.toString(); } catch (_) { // do nothing } var type = behavior.type; switch (type) { case "newWindow": if (visuals.opened) { dispatch(setVisualStatus({ opened: false })); } window.open(url, "callout-option", "toolbar=0,status=0,width=".concat(behavior.width || 1000, ",height=").concat(behavior.height || 700)); break; case "newTab": if (visuals.opened) { dispatch(setVisualStatus({ opened: false })); } window.open(url, "_blank"); break; case "sameWindow": if (!visuals.opened) { dispatch(setVisualStatus({ opened: true })); } window.open(url, "_self"); break; default: throwBadKind$1(type); } } function useOpenUrlCallback() { var env = useWidgetEnv(); return React$1.useCallback(function (url, dispatch, visuals, behavior) { if (!behavior) { behavior = (env === null || env === void 0 ? void 0 : env.urls) ? resolveUrlPolicy(url, env.urls) : defaultBehavior; } execute(url, dispatch, visuals, behavior); }, [env]); } function writeMessage(msg, user) { return { type: "synthetic", detail: { type: "write_msg", user: user, msg: msg, timestamp: +new Date() } }; } function executeAction(text, meta) { return function (chatServer) { return function (dispatch, getState) { // add in attributes from meta var attributes = getState().attributes; attributes = __assign(__assign({}, attributes), { currentUrl: window.location.href }); var _a = meta || {}, token = _a.token, rest = __rest(_a, ["token"]); attributes = __assign(__assign({}, attributes), rest); chatServer.sendChatMsg({ text: text, token: token, attributes: attributes }, function (err) { if (err) { log("Error sending message", err); return; } }); var visitor = getState().visitor; dispatch(writeMessage({ text: !token ? text : "".concat(text, " (from the list)"), }, visitor)); }; }; } /** * This is not used anymore. It was part of the handshake with the legacy "chat server" * * @param token * @returns */ function receiveToken(token) { return { type: "receiveToken", detail: { token: token, timestamp: +new Date(), type: "receiveToken", user: undefined } }; } function sendChatRating(rating) { return function (chatServer) { return function () { chatServer.sendChatRating(rating); }; }; } function sendFile(file) { return function (chatServer) { return function (dispatch, getState) { var state = getState(); // Don't allow visitor to send file if offline if (state.accountStatus === "offline" && !state.isChatting) return; // Generate attachment object for local echo var attachment = { mimeType: file.type, name: file.name, size: file.size, url: window.URL.createObjectURL(file) }; chatServer.sendFile(file, function (err) { if (err) { log("Error sending file", err); return; } }); dispatch({ type: "synthetic", detail: { type: "visitor.send.file", attachment: attachment, user: { nick: "", }, attributes: { currentUrl: window.location.href }, timestamp: +new Date() } }); }; }; } function sendGreeting(PRECHAT_FORM_ENABLED) { if (PRECHAT_FORM_ENABLED === void 0) { PRECHAT_FORM_ENABLED = false; } return function (chatServer) { return function (dispatch, getState) { if (!PRECHAT_FORM_ENABLED) { var state = getState(); if (!state.connection.greetingRequested) { log("sending greeting"); // Send nothing to get the greeting content chatServer.sendChatMsg({ text: undefined, attributes: { isGreeting: true } }, function (err2) { if (err2) { log("Error sending message"); } dispatch({ type: "sendGreeting", detail: undefined }); }); } } }; }; } function sendMessage(msg) { return function (chatServer) { return function () { chatServer.sendChatMsgRequest(msg, function () { }); }; }; } function sendOfflineMsg(data) { return function (chatServer) { return function () { chatServer.sendOfflineMsg(data, function (err) { if (err) { log(err); } }); }; }; } function sendTyping(typing) { return function (chatServer) { return function () { chatServer.sendTyping(typing); }; }; } function sendVisitorInfo(msg, sessionId) { return function (chatServer) { return function () { chatServer.setVisitorInfo(msg, sessionId, function (err) { if (err) { log(err); } }); }; }; } function setAccountStatus(status) { return { type: "account_status", detail: { status: status, timestamp: +new Date() } }; } function setConnectionStatus(status) { return { type: "connection_update", detail: { status: status, timestamp: +new Date() } }; } function sendBargeIn(agentName) { return function (chatServer) { return function () { chatServer.bargeIn(agentName, function (err) { if (err) { log(err); } }); }; }; } function sendBargeOut() { return function (chatServer) { return function () { chatServer.bargeOut(function (err) { if (err) { log(err); } }); }; }; } function reset() { return { type: "reset" }; } var LogChat = /** @class */ (function () { function LogChat(inner) { this.inner = inner; } LogChat.prototype.bargeOut = function (cb) { log("CLIENT: bargeOut"); this.inner.bargeOut(cb); }; LogChat.prototype.bargeIn = function (agentName, cb) { log("CLIENT: bargeIn: ".concat(agentName)); this.inner.bargeIn(agentName, cb); }; LogChat.prototype.init = function (dispatch) { this.inner.init(dispatch); }; LogChat.prototype.sendOfflineMsg = function (message, cb) { log("CLIENT: sendOfflineMsg: ".concat(JSON.stringify(message))); this.inner.sendOfflineMsg(message, cb); }; LogChat.prototype.sendChatMsg = function (message, cb) { log("CLIENT: sendChatMsg: ".concat(JSON.stringify(message))); this.inner.sendChatMsg(message, cb); }; LogChat.prototype.sendChatMsgRequest = function (message, cb) { log("CLIENT: sendChatMsgRequest: ".concat(JSON.stringify(message))); this.inner.sendChatMsgRequest(message, cb); }; LogChat.prototype.sendTyping = function (isTyping) { log("CLIENT: sendTyping: ".concat(JSON.stringify(isTyping))); this.inner.sendTyping(isTyping); }; LogChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) { log("CLIENT: setVisitorInfo: "