Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,75 @@
import { SDK_CSS } from './sdk.js';
import { IFrameHelper } from './IFrameHelper';
export const loadCSS = () => {
const css = document.createElement('style');
css.innerHTML = `${SDK_CSS}`;
css.id = 'cw-widget-styles';
css.dataset.turboPermanent = true;
document.body.appendChild(css);
};
// This is a method specific to Turbo
// The body replacing strategy removes Chatwoot styles
// as well as the widget, this help us get it back
export const restoreElement = (id, newBody) => {
const element = document.getElementById(id);
const newElement = newBody.querySelector(`#${id}`);
if (element && !newElement) {
newBody.appendChild(element);
}
};
export const restoreWidgetInDOM = newBody => {
restoreElement('cw-bubble-holder', newBody);
restoreElement('cw-widget-holder', newBody);
restoreElement('cw-widget-styles', newBody);
};
export const addClasses = (elm, classes) => {
elm.classList.add(...classes.split(' '));
};
export const toggleClass = (elm, classes) => {
elm.classList.toggle(classes);
};
export const removeClasses = (elm, classes) => {
elm.classList.remove(...classes.split(' '));
};
export const onLocationChange = ({ referrerURL, referrerHost }) => {
IFrameHelper.events.onLocationChange({
referrerURL,
referrerHost,
});
};
export const onLocationChangeListener = () => {
let oldHref = document.location.href;
const referrerHost = document.location.host;
const config = {
childList: true,
subtree: true,
};
onLocationChange({
referrerURL: oldHref,
referrerHost,
});
const bodyList = document.querySelector('body');
const observer = new MutationObserver(mutations => {
mutations.forEach(() => {
if (oldHref !== document.location.href) {
oldHref = document.location.href;
onLocationChange({
referrerURL: oldHref,
referrerHost,
});
}
});
});
observer.observe(bodyList, config);
};

View File

@@ -0,0 +1,345 @@
import Cookies from 'js-cookie';
import {
addClasses,
loadCSS,
removeClasses,
onLocationChangeListener,
} from './DOMHelpers';
import {
body,
widgetHolder,
createBubbleHolder,
createBubbleIcon,
bubbleSVG,
chatBubble,
closeBubble,
bubbleHolder,
onClickChatBubble,
onBubbleClick,
setBubbleText,
addUnreadClass,
removeUnreadClass,
} from './bubbleHelpers';
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
import { dispatchWindowEvent } from 'shared/helpers/CustomEventHelper';
import {
CHATWOOT_ERROR,
CHATWOOT_POSTBACK,
CHATWOOT_READY,
} from '../widget/constants/sdkEvents';
import { SET_USER_ERROR } from '../widget/constants/errorTypes';
import { getUserCookieName, setCookieWithDomain } from './cookieHelpers';
import {
getAlertAudio,
initOnEvents,
} from 'shared/helpers/AudioNotificationHelper';
import { isFlatWidgetStyle } from './settingsHelper';
import { popoutChatWindow } from '../widget/helpers/popoutHelper';
import addHours from 'date-fns/addHours';
const updateAuthCookie = (cookieContent, baseDomain = '') =>
setCookieWithDomain('cw_conversation', cookieContent, {
baseDomain,
});
const updateCampaignReadStatus = baseDomain => {
const expireBy = addHours(new Date(), 1);
setCookieWithDomain('cw_snooze_campaigns_till', Number(expireBy), {
expires: expireBy,
baseDomain,
});
};
export const IFrameHelper = {
getUrl({ baseUrl, websiteToken }) {
return `${baseUrl}/widget?website_token=${websiteToken}`;
},
createFrame: ({ baseUrl, websiteToken }) => {
if (IFrameHelper.getAppFrame()) {
return;
}
loadCSS();
const iframe = document.createElement('iframe');
const cwCookie = Cookies.get('cw_conversation');
let widgetUrl = IFrameHelper.getUrl({ baseUrl, websiteToken });
if (cwCookie) {
widgetUrl = `${widgetUrl}&cw_conversation=${cwCookie}`;
}
iframe.src = widgetUrl;
iframe.allow =
'camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;';
iframe.id = 'chatwoot_live_chat_widget';
iframe.style.visibility = 'hidden';
let holderClassName = `woot-widget-holder woot--hide woot-elements--${window.$chatwoot.position}`;
if (window.$chatwoot.hideMessageBubble) {
holderClassName += ` woot-widget--without-bubble`;
}
if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
holderClassName += ` woot-widget-holder--flat`;
}
addClasses(widgetHolder, holderClassName);
widgetHolder.id = 'cw-widget-holder';
widgetHolder.dataset.turboPermanent = true;
widgetHolder.appendChild(iframe);
body.appendChild(widgetHolder);
IFrameHelper.initPostMessageCommunication();
IFrameHelper.initWindowSizeListener();
IFrameHelper.preventDefaultScroll();
},
getAppFrame: () => document.getElementById('chatwoot_live_chat_widget'),
getBubbleHolder: () => document.getElementsByClassName('woot--bubble-holder'),
sendMessage: (key, value) => {
const element = IFrameHelper.getAppFrame();
element.contentWindow.postMessage(
`chatwoot-widget:${JSON.stringify({ event: key, ...value })}`,
'*'
);
},
initPostMessageCommunication: () => {
window.onmessage = e => {
if (
typeof e.data !== 'string' ||
e.data.indexOf('chatwoot-widget:') !== 0
) {
return;
}
const message = JSON.parse(e.data.replace('chatwoot-widget:', ''));
if (typeof IFrameHelper.events[message.event] === 'function') {
IFrameHelper.events[message.event](message);
}
};
},
initWindowSizeListener: () => {
window.addEventListener('resize', () => IFrameHelper.toggleCloseButton());
},
preventDefaultScroll: () => {
widgetHolder.addEventListener('wheel', event => {
const deltaY = event.deltaY;
const contentHeight = widgetHolder.scrollHeight;
const visibleHeight = widgetHolder.offsetHeight;
const scrollTop = widgetHolder.scrollTop;
if (
(scrollTop === 0 && deltaY < 0) ||
(visibleHeight + scrollTop === contentHeight && deltaY > 0)
) {
event.preventDefault();
}
});
},
setFrameHeightToFitContent: (extraHeight, isFixedHeight) => {
const iframe = IFrameHelper.getAppFrame();
const updatedIframeHeight = isFixedHeight ? `${extraHeight}px` : '100%';
if (iframe)
iframe.setAttribute('style', `height: ${updatedIframeHeight} !important`);
},
setupAudioListeners: () => {
const { baseUrl = '' } = window.$chatwoot;
getAlertAudio(baseUrl, { type: 'widget', alertTone: 'ding' }).then(() =>
initOnEvents.forEach(event => {
document.removeEventListener(
event,
IFrameHelper.setupAudioListeners,
false
);
})
);
},
events: {
loaded: message => {
updateAuthCookie(message.config.authToken, window.$chatwoot.baseDomain);
window.$chatwoot.hasLoaded = true;
const campaignsSnoozedTill = Cookies.get('cw_snooze_campaigns_till');
IFrameHelper.sendMessage('config-set', {
locale: window.$chatwoot.locale,
position: window.$chatwoot.position,
hideMessageBubble: window.$chatwoot.hideMessageBubble,
showPopoutButton: window.$chatwoot.showPopoutButton,
widgetStyle: window.$chatwoot.widgetStyle,
darkMode: window.$chatwoot.darkMode,
showUnreadMessagesDialog: window.$chatwoot.showUnreadMessagesDialog,
campaignsSnoozedTill,
welcomeTitle: window.$chatwoot.welcomeTitle,
welcomeDescription: window.$chatwoot.welcomeDescription,
availableMessage: window.$chatwoot.availableMessage,
unavailableMessage: window.$chatwoot.unavailableMessage,
enableFileUpload: window.$chatwoot.enableFileUpload,
enableEmojiPicker: window.$chatwoot.enableEmojiPicker,
enableEndConversation: window.$chatwoot.enableEndConversation,
});
IFrameHelper.onLoad({
widgetColor: message.config.channelConfig.widgetColor,
});
IFrameHelper.toggleCloseButton();
if (window.$chatwoot.user) {
IFrameHelper.sendMessage('set-user', window.$chatwoot.user);
}
window.playAudioAlert = () => {};
initOnEvents.forEach(e => {
document.addEventListener(e, IFrameHelper.setupAudioListeners, false);
});
if (!window.$chatwoot.resetTriggered) {
dispatchWindowEvent({ eventName: CHATWOOT_READY });
}
},
error: ({ errorType, data }) => {
dispatchWindowEvent({ eventName: CHATWOOT_ERROR, data: data });
if (errorType === SET_USER_ERROR) {
Cookies.remove(getUserCookieName());
}
},
onEvent({ eventIdentifier: eventName, data }) {
dispatchWindowEvent({ eventName, data });
},
setBubbleLabel(message) {
setBubbleText(window.$chatwoot.launcherTitle || message.label);
},
setAuthCookie({ data: { widgetAuthToken } }) {
updateAuthCookie(widgetAuthToken, window.$chatwoot.baseDomain);
},
setCampaignReadOn() {
updateCampaignReadStatus(window.$chatwoot.baseDomain);
},
postback(data) {
dispatchWindowEvent({
eventName: CHATWOOT_POSTBACK,
data,
});
},
toggleBubble: state => {
let bubbleState = {};
if (state === 'open') {
bubbleState.toggleValue = true;
} else if (state === 'close') {
bubbleState.toggleValue = false;
}
onBubbleClick(bubbleState);
},
popoutChatWindow: ({ baseUrl, websiteToken, locale }) => {
const cwCookie = Cookies.get('cw_conversation');
window.$chatwoot.toggle('close');
popoutChatWindow(baseUrl, websiteToken, locale, cwCookie);
},
closeWindow: () => {
onBubbleClick({ toggleValue: false });
removeUnreadClass();
},
onBubbleToggle: isOpen => {
IFrameHelper.sendMessage('toggle-open', { isOpen });
if (isOpen) {
IFrameHelper.pushEvent('webwidget.triggered');
}
},
onLocationChange: ({ referrerURL, referrerHost }) => {
IFrameHelper.sendMessage('change-url', {
referrerURL,
referrerHost,
});
},
updateIframeHeight: message => {
const { extraHeight = 0, isFixedHeight } = message;
IFrameHelper.setFrameHeightToFitContent(extraHeight, isFixedHeight);
},
setUnreadMode: () => {
addUnreadClass();
onBubbleClick({ toggleValue: true });
},
resetUnreadMode: () => removeUnreadClass(),
handleNotificationDot: event => {
if (window.$chatwoot.hideMessageBubble) {
return;
}
const bubbleElement = document.querySelector('.woot-widget-bubble');
if (
event.unreadMessageCount > 0 &&
!bubbleElement.classList.contains('unread-notification')
) {
addClasses(bubbleElement, 'unread-notification');
} else if (event.unreadMessageCount === 0) {
removeClasses(bubbleElement, 'unread-notification');
}
},
closeChat: () => {
onBubbleClick({ toggleValue: false });
},
playAudio: () => {
window.playAudioAlert();
},
},
pushEvent: eventName => {
IFrameHelper.sendMessage('push-event', { eventName });
},
onLoad: ({ widgetColor }) => {
const iframe = IFrameHelper.getAppFrame();
iframe.style.visibility = '';
iframe.setAttribute('id', `chatwoot_live_chat_widget`);
if (IFrameHelper.getBubbleHolder().length) {
return;
}
createBubbleHolder(window.$chatwoot.hideMessageBubble);
onLocationChangeListener();
let className = 'woot-widget-bubble';
let closeBtnClassName = `woot-elements--${window.$chatwoot.position} woot-widget-bubble woot--close woot--hide`;
if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
className += ' woot-widget-bubble--flat';
closeBtnClassName += ' woot-widget-bubble--flat';
}
if (isWidgetColorLighter(widgetColor)) {
className += ' woot-widget-bubble-color--lighter';
closeBtnClassName += ' woot-widget-bubble-color--lighter';
}
const chatIcon = createBubbleIcon({
className,
path: bubbleSVG,
target: chatBubble,
});
addClasses(closeBubble, closeBtnClassName);
chatIcon.style.background = widgetColor;
closeBubble.style.background = widgetColor;
bubbleHolder.appendChild(chatIcon);
bubbleHolder.appendChild(closeBubble);
onClickChatBubble();
},
toggleCloseButton: () => {
let isMobile = false;
if (window.matchMedia('(max-width: 668px)').matches) {
isMobile = true;
}
IFrameHelper.sendMessage('toggle-close-button', { isMobile });
},
};

View File

@@ -0,0 +1,112 @@
import { addClasses, removeClasses, toggleClass } from './DOMHelpers';
import { IFrameHelper } from './IFrameHelper';
import { isExpandedView } from './settingsHelper';
import {
CHATWOOT_CLOSED,
CHATWOOT_OPENED,
} from '../widget/constants/sdkEvents';
import { dispatchWindowEvent } from 'shared/helpers/CustomEventHelper';
export const bubbleSVG =
'M240.808 240.808H122.123C56.6994 240.808 3.45695 187.562 3.45695 122.122C3.45695 56.7031 56.6994 3.45697 122.124 3.45697C187.566 3.45697 240.808 56.7031 240.808 122.122V240.808Z';
export const body = document.getElementsByTagName('body')[0];
export const widgetHolder = document.createElement('div');
export const bubbleHolder = document.createElement('div');
export const chatBubble = document.createElement('button');
export const closeBubble = document.createElement('button');
export const notificationBubble = document.createElement('span');
export const setBubbleText = bubbleText => {
if (isExpandedView(window.$chatwoot.type)) {
const textNode = document.getElementById('woot-widget--expanded__text');
textNode.innerText = bubbleText;
}
};
export const createBubbleIcon = ({ className, path, target }) => {
let bubbleClassName = `${className} woot-elements--${window.$chatwoot.position}`;
const bubbleIcon = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
bubbleIcon.setAttributeNS(null, 'id', 'woot-widget-bubble-icon');
bubbleIcon.setAttributeNS(null, 'width', '24');
bubbleIcon.setAttributeNS(null, 'height', '24');
bubbleIcon.setAttributeNS(null, 'viewBox', '0 0 240 240');
bubbleIcon.setAttributeNS(null, 'fill', 'none');
bubbleIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
const bubblePath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
bubblePath.setAttributeNS(null, 'd', path);
bubblePath.setAttributeNS(null, 'fill', '#FFFFFF');
bubbleIcon.appendChild(bubblePath);
target.appendChild(bubbleIcon);
if (isExpandedView(window.$chatwoot.type)) {
const textNode = document.createElement('div');
textNode.id = 'woot-widget--expanded__text';
textNode.innerText = '';
target.appendChild(textNode);
bubbleClassName += ' woot-widget--expanded';
}
target.className = bubbleClassName;
target.title = 'Open chat window';
return target;
};
export const createBubbleHolder = hideMessageBubble => {
if (hideMessageBubble) {
addClasses(bubbleHolder, 'woot-hidden');
}
addClasses(bubbleHolder, 'woot--bubble-holder');
bubbleHolder.id = 'cw-bubble-holder';
bubbleHolder.dataset.turboPermanent = true;
body.appendChild(bubbleHolder);
};
const handleBubbleToggle = newIsOpen => {
IFrameHelper.events.onBubbleToggle(newIsOpen);
if (newIsOpen) {
dispatchWindowEvent({ eventName: CHATWOOT_OPENED });
} else {
dispatchWindowEvent({ eventName: CHATWOOT_CLOSED });
chatBubble.focus();
}
};
export const onBubbleClick = (props = {}) => {
const { toggleValue } = props;
const { isOpen } = window.$chatwoot;
if (isOpen === toggleValue) return;
const newIsOpen = toggleValue === undefined ? !isOpen : toggleValue;
window.$chatwoot.isOpen = newIsOpen;
toggleClass(chatBubble, 'woot--hide');
toggleClass(closeBubble, 'woot--hide');
toggleClass(widgetHolder, 'woot--hide');
handleBubbleToggle(newIsOpen);
};
export const onClickChatBubble = () => {
bubbleHolder.addEventListener('click', onBubbleClick);
};
export const addUnreadClass = () => {
const holderEl = document.querySelector('.woot-widget-holder');
addClasses(holderEl, 'has-unread-view');
};
export const removeUnreadClass = () => {
const holderEl = document.querySelector('.woot-widget-holder');
removeClasses(holderEl, 'has-unread-view');
};

View File

@@ -0,0 +1,3 @@
export const BUBBLE_DESIGN = ['standard', 'expanded_bubble'];
export const WIDGET_DESIGN = ['standard', 'flat'];
export const DARK_MODE = ['light', 'auto', 'dark'];

View File

@@ -0,0 +1,45 @@
import md5 from 'md5';
import Cookies from 'js-cookie';
const REQUIRED_USER_KEYS = ['avatar_url', 'email', 'name'];
const ALLOWED_USER_ATTRIBUTES = [...REQUIRED_USER_KEYS, 'identifier_hash'];
export const getUserCookieName = () => {
const SET_USER_COOKIE_PREFIX = 'cw_user_';
const { websiteToken: websiteIdentifier } = window.$chatwoot;
return `${SET_USER_COOKIE_PREFIX}${websiteIdentifier}`;
};
export const getUserString = ({ identifier = '', user }) => {
const userStringWithSortedKeys = ALLOWED_USER_ATTRIBUTES.reduce(
(acc, key) => `${acc}${key}${user[key] || ''}`,
''
);
return `${userStringWithSortedKeys}identifier${identifier}`;
};
export const computeHashForUserData = (...args) => md5(getUserString(...args));
export const hasUserKeys = user =>
REQUIRED_USER_KEYS.reduce((acc, key) => acc || !!user[key], false);
export const setCookieWithDomain = (
name,
value,
{ expires = 365, baseDomain = undefined } = {}
) => {
const cookieOptions = {
expires,
sameSite: 'Lax',
domain: baseDomain,
};
// if type of value is object, stringify it
// this is because js-cookies 3.0 removed builtin json support
// ref: https://github.com/js-cookie/js-cookie/releases/tag/v3.0.0
if (typeof value === 'object') {
value = JSON.stringify(value);
}
Cookies.set(name, value, cookieOptions);
};

View File

@@ -0,0 +1,295 @@
export const SDK_CSS = `
:root {
--b-100: #F2F3F7;
--s-700: #37546D;
}
.woot-widget-holder {
box-shadow: 0 5px 40px rgba(0, 0, 0, .16);
opacity: 1;
will-change: transform, opacity;
transform: translateY(0);
overflow: hidden !important;
position: fixed !important;
transition: opacity 0.2s linear, transform 0.25s linear;
z-index: 2147483000 !important;
}
.woot-widget-holder.woot-widget-holder--flat {
box-shadow: none;
border-radius: 0;
border: 1px solid var(--b-100);
}
.woot-widget-holder iframe {
border: 0;
color-scheme: normal;
height: 100% !important;
width: 100% !important;
max-height: 100vh !important;
}
.woot-widget-holder.has-unread-view {
border-radius: 0 !important;
min-height: 80px !important;
height: auto;
bottom: 94px;
box-shadow: none !important;
border: 0;
}
.woot-widget-bubble {
background: #1f93ff;
border-radius: 100px;
border-width: 0px;
bottom: 20px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .16) !important;
cursor: pointer;
height: 64px;
padding: 0px;
position: fixed;
user-select: none;
width: 64px;
z-index: 2147483000 !important;
overflow: hidden;
}
.woot-widget-bubble.woot-widget-bubble--flat {
border-radius: 0;
}
.woot-widget-holder.woot-widget-holder--flat {
bottom: 90px;
}
.woot-widget-bubble.woot-widget-bubble--flat {
height: 56px;
width: 56px;
}
.woot-widget-bubble.woot-widget-bubble--flat svg {
margin: 16px;
}
.woot-widget-bubble.woot-widget-bubble--flat.woot--close::before,
.woot-widget-bubble.woot-widget-bubble--flat.woot--close::after {
left: 28px;
top: 16px;
}
.woot-widget-bubble.unread-notification::after {
content: '';
position: absolute;
width: 12px;
height: 12px;
background: #ff4040;
border-radius: 100%;
top: 0px;
right: 0px;
border: 2px solid #ffffff;
transition: background 0.2s ease;
}
.woot-widget-bubble.woot-widget--expanded {
bottom: 24px;
display: flex;
height: 48px !important;
width: auto !important;
align-items: center;
}
.woot-widget-bubble.woot-widget--expanded div {
align-items: center;
color: #fff;
display: flex;
font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif;
font-size: 16px;
font-weight: 500;
justify-content: center;
padding-right: 20px;
width: auto !important;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble-color--lighter div{
color: var(--s-700);
}
.woot-widget-bubble.woot-widget--expanded svg {
height: 20px;
margin: 14px 8px 14px 16px;
width: 20px;
}
.woot-widget-bubble.woot-elements--left {
left: 20px;
}
.woot-widget-bubble.woot-elements--right {
right: 20px;
}
.woot-widget-bubble:hover {
background: #1f93ff;
box-shadow: 0 8px 32px rgba(0, 0, 0, .4) !important;
}
.woot-widget-bubble svg {
all: revert;
height: 24px;
margin: 20px;
width: 24px;
}
.woot-widget-bubble.woot-widget-bubble-color--lighter path{
fill: var(--s-700);
}
@media only screen and (min-width: 667px) {
.woot-widget-holder.woot-elements--left {
left: 20px;
}
.woot-widget-holder.woot-elements--right {
right: 20px;
}
}
.woot--close:hover {
opacity: 1;
}
.woot--close::before, .woot--close::after {
background-color: #fff;
content: ' ';
display: inline;
height: 24px;
left: 32px;
position: absolute;
top: 20px;
width: 2px;
}
.woot-widget-bubble-color--lighter.woot--close::before, .woot-widget-bubble-color--lighter.woot--close::after {
background-color: var(--s-700);
}
.woot--close::before {
transform: rotate(45deg);
}
.woot--close::after {
transform: rotate(-45deg);
}
.woot--hide {
bottom: -100vh !important;
top: unset !important;
opacity: 0;
visibility: hidden !important;
z-index: -1 !important;
}
.woot-widget--without-bubble {
bottom: 20px !important;
}
.woot-widget-holder.woot--hide{
transform: translateY(40px);
}
.woot-widget-bubble.woot--close {
transform: translateX(0px) scale(1) rotate(0deg);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot--close.woot--hide {
transform: translateX(8px) scale(.75) rotate(45deg);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
}
.woot-widget-bubble {
transform-origin: center;
will-change: transform, opacity;
transform: translateX(0) scale(1) rotate(0deg);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot--hide {
transform: translateX(8px) scale(.75) rotate(-30deg);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget--expanded {
transform: translateX(0px);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget--expanded.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget-bubble--flat.woot--close {
transform: translateX(0px);
transition: transform 300ms ease, opacity 10ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget-bubble--flat.woot--close.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble--flat {
transform: translateX(0px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble--flat.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
}
@media only screen and (max-width: 667px) {
.woot-widget-holder {
height: 100%;
right: 0;
top: 0;
width: 100%;
}
.woot-widget-holder iframe {
min-height: 100% !important;
}
.woot-widget-holder.has-unread-view {
height: auto;
right: 0;
width: auto;
bottom: 0;
top: auto;
max-height: 100vh;
padding: 0 8px;
}
.woot-widget-holder.has-unread-view iframe {
min-height: unset !important;
}
.woot-widget-holder.has-unread-view.woot-elements--left {
left: 0;
}
.woot-widget-bubble.woot--close {
bottom: 60px;
opacity: 0;
visibility: hidden !important;
z-index: -1 !important;
}
}
@media only screen and (min-width: 667px) {
.woot-widget-holder {
border-radius: 16px;
bottom: 104px;
height: calc(90% - 64px - 20px);
max-height: 640px !important;
min-height: 250px !important;
width: 400px !important;
}
}
.woot-hidden {
display: none !important;
}
`;

View File

@@ -0,0 +1,14 @@
import { BUBBLE_DESIGN, DARK_MODE, WIDGET_DESIGN } from './constants';
export const getBubbleView = type =>
BUBBLE_DESIGN.includes(type) ? type : BUBBLE_DESIGN[0];
export const isExpandedView = type => getBubbleView(type) === BUBBLE_DESIGN[1];
export const getWidgetStyle = style =>
WIDGET_DESIGN.includes(style) ? style : WIDGET_DESIGN[0];
export const isFlatWidgetStyle = style => style === 'flat';
export const getDarkMode = darkMode =>
DARK_MODE.includes(darkMode) ? darkMode : DARK_MODE[0];

View File

@@ -0,0 +1,130 @@
import Cookies from 'js-cookie';
import {
getUserCookieName,
getUserString,
hasUserKeys,
setCookieWithDomain,
} from '../cookieHelpers';
describe('#getUserCookieName', () => {
it('returns correct cookie name', () => {
global.$chatwoot = { websiteToken: '123456' };
expect(getUserCookieName()).toBe('cw_user_123456');
});
});
describe('#getUserString', () => {
it('returns correct user string', () => {
expect(
getUserString({
user: {
name: 'Pranav',
email: 'pranav@example.com',
avatar_url: 'https://images.chatwoot.com/placeholder',
identifier_hash: '12345',
},
identifier: '12345',
})
).toBe(
'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnamePranavidentifier_hash12345identifier12345'
);
expect(
getUserString({
user: {
email: 'pranav@example.com',
avatar_url: 'https://images.chatwoot.com/placeholder',
},
})
).toBe(
'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnameidentifier_hashidentifier'
);
});
});
describe('#hasUserKeys', () => {
it('checks whether the allowed list of keys are present', () => {
expect(hasUserKeys({})).toBe(false);
expect(hasUserKeys({ randomKey: 'randomValue' })).toBe(false);
expect(hasUserKeys({ avatar_url: 'randomValue' })).toBe(true);
});
});
// Mock the 'set' method of the 'Cookies' object
describe('setCookieWithDomain', () => {
beforeEach(() => {
vi.spyOn(Cookies, 'set');
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should set a cookie with default parameters', () => {
setCookieWithDomain('myCookie', 'cookieValue');
expect(Cookies.set).toHaveBeenCalledWith('myCookie', 'cookieValue', {
expires: 365,
sameSite: 'Lax',
domain: undefined,
});
});
it('should set a cookie with custom expiration and sameSite attribute', () => {
setCookieWithDomain('myCookie', 'cookieValue', {
expires: 30,
});
expect(Cookies.set).toHaveBeenCalledWith('myCookie', 'cookieValue', {
expires: 30,
sameSite: 'Lax',
domain: undefined,
});
});
it('should set a cookie with a specific base domain', () => {
setCookieWithDomain('myCookie', 'cookieValue', {
baseDomain: 'example.com',
});
expect(Cookies.set).toHaveBeenCalledWith('myCookie', 'cookieValue', {
expires: 365,
sameSite: 'Lax',
domain: 'example.com',
});
});
it('should stringify the cookie value when setting the value', () => {
setCookieWithDomain(
'myCookie',
{ value: 'cookieValue' },
{
baseDomain: 'example.com',
}
);
expect(Cookies.set).toHaveBeenCalledWith(
'myCookie',
JSON.stringify({ value: 'cookieValue' }),
{
expires: 365,
sameSite: 'Lax',
domain: 'example.com',
}
);
});
it('should set a cookie with custom expiration, sameSite attribute, and specific base domain', () => {
setCookieWithDomain('myCookie', 'cookieValue', {
expires: 7,
baseDomain: 'example.com',
});
expect(Cookies.set).toHaveBeenCalledWith('myCookie', 'cookieValue', {
expires: 7,
sameSite: 'Lax',
domain: 'example.com',
});
});
});

View File

@@ -0,0 +1,38 @@
import {
getBubbleView,
getWidgetStyle,
isExpandedView,
isFlatWidgetStyle,
} from '../settingsHelper';
describe('#getBubbleView', () => {
it('returns correct view', () => {
expect(getBubbleView('')).toEqual('standard');
expect(getBubbleView('standard')).toEqual('standard');
expect(getBubbleView('expanded_bubble')).toEqual('expanded_bubble');
});
});
describe('#isExpandedView', () => {
it('returns true if it is expanded view', () => {
expect(isExpandedView('')).toEqual(false);
expect(isExpandedView('standard')).toEqual(false);
expect(isExpandedView('expanded_bubble')).toEqual(true);
});
});
describe('#getWidgetStyle', () => {
it('returns correct view', () => {
expect(getWidgetStyle('')).toEqual('standard');
expect(getWidgetStyle('standard')).toEqual('standard');
expect(getWidgetStyle('flat')).toEqual('flat');
});
});
describe('#isFlatWidgetStyle', () => {
it('returns true if it is expanded view', () => {
expect(isFlatWidgetStyle('')).toEqual(false);
expect(isFlatWidgetStyle('standard')).toEqual(false);
expect(isFlatWidgetStyle('flat')).toEqual(true);
});
});