You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 7-8.RSVP
Socket
Socket
Sign inDemoInstall

@papercups-io/chat-widget

Package Overview
Dependencies
Maintainers
2
Versions
113
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.15 to 1.0.16-beta.0

875

dist/index.js

@@ -6,754 +6,5 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var themeUi = require('theme-ui');
var phoenix = require('phoenix');
var dayjs = _interopDefault(require('dayjs'));
var utc = _interopDefault(require('dayjs/plugin/utc'));
var request = _interopDefault(require('superagent'));
var qs = _interopDefault(require('query-string'));
var tinycolor = _interopDefault(require('tinycolor2'));
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
// A type of promise-like that resolves synchronously and supports only one observer
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator";
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator";
// Asynchronously call a function and send errors to recovery continuation
function _catch(body, recover) {
try {
var result = body();
} catch(e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
var BotIcon = function BotIcon(_ref) {
var width = _ref.width,
height = _ref.height,
fill = _ref.fill,
className = _ref.className;
return themeUi.jsx("svg", {
viewBox: '64 64 896 896',
focusable: 'false',
className: className,
"data-icon": 'robot',
width: width || '1em',
height: height || '1em',
sx: {
fill: fill || 'black'
}
}, themeUi.jsx("path", {
d: 'M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z'
}));
};
dayjs.extend(utc);
var formatRelativeTime = function formatRelativeTime(date) {
var ms = dayjs().diff(date, 'second');
var mins = Math.floor(ms / 60);
var hrs = Math.floor(mins / 60);
var days = Math.floor(hrs / 24);
if (ms < 10) {
return 'just now';
} else if (ms < 60) {
return ms + " seconds ago";
} else if (mins <= 60) {
return mins + " minute" + (mins === 1 ? '' : 's') + " ago";
} else if (hrs <= 24) {
return hrs + " hour" + (hrs === 1 ? '' : 's') + " ago";
} else {
return days + " day" + (days === 1 ? '' : 's') + " ago";
}
};
var getAgentIdentifier = function getAgentIdentifier(user) {
if (!user) {
return 'Agent';
}
var display_name = user.display_name,
full_name = user.full_name,
email = user.email;
var _email$split = email.split('@'),
username = _email$split[0];
return display_name || full_name || username || 'Agent';
};
var SenderAvatar = function SenderAvatar(_ref) {
var name = _ref.name,
user = _ref.user,
isBot = _ref.isBot;
var profilePhotoUrl = user && user.profile_photo_url;
if (profilePhotoUrl) {
return React.createElement(themeUi.Box, {
mr: 2,
style: {
height: 32,
width: 32,
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundImage: "url(" + profilePhotoUrl + ")"
}
});
}
return React.createElement(themeUi.Flex, {
mr: 2,
sx: {
bg: isBot ? 'gray' : 'primary',
height: 32,
width: 32,
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
color: '#fff'
}
}, isBot ? React.createElement(BotIcon, {
fill: 'background',
height: 16,
width: 16
}) : name.slice(0, 1).toUpperCase());
};
var ChatMessage = function ChatMessage(_ref2) {
var message = _ref2.message,
isMe = _ref2.isMe,
isLastInGroup = _ref2.isLastInGroup,
shouldDisplayTimestamp = _ref2.shouldDisplayTimestamp;
var body = message.body,
created_at = message.created_at,
user = message.user,
type = message.type;
var created = dayjs.utc(created_at);
var timestamp = formatRelativeTime(created);
var isBot = type === 'bot';
var identifer = isBot ? 'Bot' : getAgentIdentifier(user);
if (isMe) {
return React.createElement(themeUi.Box, {
pr: 0,
pl: 4,
pb: isLastInGroup ? 3 : 2
}, React.createElement(themeUi.Flex, {
sx: {
justifyContent: 'flex-end'
}
}, React.createElement(themeUi.Box, {
px: '14px',
py: 2,
sx: {
color: 'background',
bg: 'primary',
borderRadius: 4
}
}, React.createElement(themeUi.Text, null, body))), shouldDisplayTimestamp && React.createElement(themeUi.Flex, {
m: 1,
sx: {
justifyContent: 'flex-end'
}
}, React.createElement(themeUi.Text, {
sx: {
color: 'gray'
}
}, "Sent ", timestamp)));
}
return React.createElement(themeUi.Box, {
pr: 4,
pl: 0,
pb: isLastInGroup ? 3 : 2
}, React.createElement(themeUi.Flex, {
sx: {
justifyContent: 'flex-start',
alignItems: 'center'
}
}, React.createElement(SenderAvatar, {
name: identifer,
user: user,
isBot: isBot
}), React.createElement(themeUi.Box, {
px: '14px',
py: 2,
sx: {
color: 'text',
bg: 'rgb(245, 245, 245)',
borderRadius: 4,
maxWidth: '80%'
}
}, body)), shouldDisplayTimestamp && React.createElement(themeUi.Flex, {
m: 1,
sx: {
justifyContent: 'flex-start'
}
}, React.createElement(themeUi.Text, {
sx: {
color: 'gray'
}
}, identifer, " \xB7 Sent ", timestamp)));
};
var SendIcon = function SendIcon(_ref) {
var width = _ref.width,
height = _ref.height,
fill = _ref.fill,
className = _ref.className;
return themeUi.jsx("svg", {
focusable: 'false',
"aria-hidden": 'true',
height: height || 16,
width: width || 16,
viewBox: '0 0 15 16',
className: className,
sx: {
fill: fill || 'black'
}
}, themeUi.jsx("g", {
transform: 'translate(-24.000000, -12.000000)'
}, themeUi.jsx("path", {
d: 'M25.4036262,27.3409362 C24.4176893,27.8509036 23.8195834,27.3951055 24.0683403,26.3201996 L25.0887779,21.910776 C25.2131242,21.3734618 25.7510472,20.8884231 26.3078778,20.8254187 L32.503417,20.1244045 C34.151155,19.9379658 34.1569707,19.6389088 32.503417,19.4549971 L26.3078778,18.7659164 C25.7589338,18.7048617 25.2129433,18.217839 25.0887779,17.6798715 L24.0683403,13.2586546 C23.8198614,12.1820783 24.408944,11.7182276 25.4036262,12.2327184 L38.22304,18.8634497 C39.208977,19.373417 39.2177223,20.1957141 38.22304,20.7102049 L25.4036262,27.3409362 Z'
})));
};
var DEFAULT_BASE_URL = 'https://app.papercups.io';
var getWebsocketUrl = function getWebsocketUrl(baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
var _baseUrl$split = baseUrl.split('://'),
protocol = _baseUrl$split[0],
host = _baseUrl$split[1];
var isHttps = protocol === 'https';
return (isHttps ? 'wss' : 'ws') + "://" + host + "/socket";
};
function now() {
var date = new Date();
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
var EMPTY_METADATA = {};
var createNewCustomer = function createNewCustomer(accountId, metadata, baseUrl) {
if (metadata === void 0) {
metadata = EMPTY_METADATA;
}
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.post(baseUrl + "/api/customers").send({
customer: _extends({}, metadata, {
account_id: accountId,
first_seen: now(),
last_seen: now()
})
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var updateCustomerMetadata = function updateCustomerMetadata(customerId, metadata, baseUrl) {
if (metadata === void 0) {
metadata = EMPTY_METADATA;
}
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.put(baseUrl + "/api/customers/" + customerId + "/metadata").send({
metadata: metadata
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var createNewConversation = function createNewConversation(accountId, customerId, baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.post(baseUrl + "/api/conversations").send({
conversation: {
account_id: accountId,
customer_id: customerId
}
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var fetchCustomerConversations = function fetchCustomerConversations(customerId, accountId, baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.get(baseUrl + "/api/conversations/customer").query({
customer_id: customerId,
account_id: accountId
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var PREFIX = '__PAPERCUPS__';
var get = function get(key) {
var result = localStorage.getItem("" + PREFIX + key);
if (!result) {
return null;
}
try {
return JSON.parse(result);
} catch (e) {
return result;
}
};
var set = function set(key, value) {
localStorage.setItem("" + PREFIX + key, JSON.stringify(value));
};
var getCustomerId = function getCustomerId() {
return get('__CUSTOMER_ID__');
};
var setCustomerId = function setCustomerId(id) {
return set('__CUSTOMER_ID__', id);
};
var ChatWindow = /*#__PURE__*/function (_React$Component) {
_inheritsLoose(ChatWindow, _React$Component);
function ChatWindow() {
var _this;
_this = _React$Component.apply(this, arguments) || this;
_this.scrollToEl = null;
_this.state = {
message: '',
messages: [],
customerId: getCustomerId(),
conversationId: null,
isSending: false
};
_this.getDefaultGreeting = function () {
var greeting = _this.props.greeting;
if (!greeting) {
return [];
}
return [{
type: 'bot',
body: greeting,
created_at: now().toString()
}];
};
_this.fetchLatestConversation = function (customerId) {
try {
if (!customerId) {
_this.setState({
messages: [].concat(_this.getDefaultGreeting())
});
return Promise.resolve();
}
var _this$props = _this.props,
accountId = _this$props.accountId,
baseUrl = _this$props.baseUrl,
metadata = _this$props.customer;
console.log('Fetching conversations for customer:', customerId);
return Promise.resolve(_catch(function () {
return Promise.resolve(fetchCustomerConversations(customerId, accountId, baseUrl)).then(function (conversations) {
console.log('Found existing conversations:', conversations);
if (!conversations || !conversations.length) {
_this.setState({
messages: [].concat(_this.getDefaultGreeting())
});
return;
}
var latest = conversations[0];
var conversationId = latest.id,
_latest$messages = latest.messages,
messages = _latest$messages === void 0 ? [] : _latest$messages;
var formattedMessages = messages.sort(function (a, b) {
return +new Date(a.created_at) - +new Date(b.created_at);
});
_this.setState({
conversationId: conversationId,
messages: [].concat(_this.getDefaultGreeting(), formattedMessages)
});
_this.joinConversationChannel(conversationId, customerId);
return Promise.resolve(_this.updateExistingCustomer(customerId, metadata)).then(function () {});
});
}, function (err) {
console.log('Error fetching conversations!', err);
}));
} catch (e) {
return Promise.reject(e);
}
};
_this.createNewCustomerId = function (accountId) {
try {
var _this$props2 = _this.props,
baseUrl = _this$props2.baseUrl,
metadata = _this$props2.customer;
return Promise.resolve(createNewCustomer(accountId, metadata, baseUrl)).then(function (_ref) {
var customerId = _ref.id;
setCustomerId(customerId);
return customerId;
});
} catch (e) {
return Promise.reject(e);
}
};
_this.updateExistingCustomer = function (customerId, metadata) {
try {
if (!metadata) {
return Promise.resolve();
}
var _temp2 = _catch(function () {
var baseUrl = _this.props.baseUrl;
return Promise.resolve(updateCustomerMetadata(customerId, metadata, baseUrl)).then(function () {});
}, function (err) {
console.log('Error updating customer metadata!', err);
});
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0);
} catch (e) {
return Promise.reject(e);
}
};
_this.initializeNewConversation = function () {
try {
var _this$props3 = _this.props,
accountId = _this$props3.accountId,
baseUrl = _this$props3.baseUrl;
return Promise.resolve(_this.createNewCustomerId(accountId)).then(function (customerId) {
return Promise.resolve(createNewConversation(accountId, customerId, baseUrl)).then(function (_ref2) {
var conversationId = _ref2.id;
_this.setState({
customerId: customerId,
conversationId: conversationId
});
_this.joinConversationChannel(conversationId, customerId);
return {
customerId: customerId,
conversationId: conversationId
};
});
});
} catch (e) {
return Promise.reject(e);
}
};
_this.joinConversationChannel = function (conversationId, customerId) {
if (_this.channel && _this.channel.leave) {
_this.channel.leave();
}
console.log('Joining channel:', conversationId);
_this.channel = _this.socket.channel("conversation:" + conversationId, {
customer_id: customerId
});
_this.channel.on('shout', function (message) {
_this.handleNewMessage(message);
});
_this.channel.join().receive('ok', function (res) {
console.log('Joined successfully!', res);
}).receive('error', function (err) {
console.log('Unable to join!', err);
});
_this.scrollToEl.scrollIntoView();
};
_this.handleNewMessage = function (message) {
_this.setState({
messages: [].concat(_this.state.messages, [message])
}, function () {
_this.scrollToEl.scrollIntoView();
});
};
_this.handleMessageChange = function (e) {
_this.setState({
message: e.target.value
});
};
_this.handleKeyDown = function (e) {
if (e.key === 'Enter') {
_this.handleSendMessage(e);
}
};
_this.handleSendMessage = function (e) {
try {
var _temp5 = function _temp5() {
if (!_this.channel) {
_this.setState({
isSending: false
});
return;
}
_this.channel.push('shout', {
body: message,
customer_id: _this.state.customerId
});
_this.setState({
message: '',
isSending: false
});
};
e && e.preventDefault();
var _this$state = _this.state,
message = _this$state.message,
customerId = _this$state.customerId,
conversationId = _this$state.conversationId,
isSending = _this$state.isSending;
if (isSending || !message || message.trim().length === 0) {
return Promise.resolve();
}
_this.setState({
isSending: true
});
var _temp6 = function () {
if (!customerId || !conversationId) {
return Promise.resolve(_this.initializeNewConversation()).then(function () {});
}
}();
return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6));
} catch (e) {
return Promise.reject(e);
}
};
return _this;
}
var _proto = ChatWindow.prototype;
_proto.componentDidMount = function componentDidMount() {
var websocketUrl = getWebsocketUrl(this.props.baseUrl);
this.socket = new phoenix.Socket(websocketUrl);
this.socket.connect();
this.fetchLatestConversation(this.state.customerId);
};
_proto.componentWillUnmount = function componentWillUnmount() {
this.channel && this.channel.leave();
};
_proto.render = function render() {
var _this2 = this;
var _this$props4 = this.props,
_this$props4$title = _this$props4.title,
title = _this$props4$title === void 0 ? 'Welcome!' : _this$props4$title,
_this$props4$subtitle = _this$props4.subtitle,
subtitle = _this$props4$subtitle === void 0 ? 'How can we help you?' : _this$props4$subtitle;
var _this$state2 = this.state,
customerId = _this$state2.customerId,
message = _this$state2.message,
_this$state2$messages = _this$state2.messages,
messages = _this$state2$messages === void 0 ? [] : _this$state2$messages,
isSending = _this$state2.isSending;
return React.createElement(themeUi.Flex, {
sx: {
bg: 'background',
flexDirection: 'column',
height: '100%',
width: '100%'
}
}, React.createElement(themeUi.Box, {
py: 3,
px: 4,
sx: {
bg: 'primary'
}
}, React.createElement(themeUi.Heading, {
as: 'h2',
className: 'Papercups-heading',
sx: {
color: 'background',
my: 1
}
}, title), React.createElement(themeUi.Text, {
sx: {
color: 'offset'
}
}, subtitle)), React.createElement(themeUi.Box, {
p: 3,
sx: {
flex: 1,
boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset',
overflowY: 'scroll'
}
}, messages.map(function (msg, key) {
var next = messages[key + 1];
var isLastInGroup = next ? msg.customer_id !== next.customer_id : true;
var shouldDisplayTimestamp = key === messages.length - 1;
var isMe = msg.customer_id === customerId;
return React.createElement(framerMotion.motion.div, {
key: key,
initial: {
opacity: 0,
x: isMe ? 2 : -2
},
animate: {
opacity: 1,
x: 0
},
transition: {
duration: 0.2,
ease: 'easeIn'
}
}, React.createElement(ChatMessage, {
key: key,
message: msg,
isMe: isMe,
isLastInGroup: isLastInGroup,
shouldDisplayTimestamp: shouldDisplayTimestamp
}));
}), React.createElement("div", {
ref: function ref(el) {
return _this2.scrollToEl = el;
}
})), React.createElement(themeUi.Box, {
p: 2,
sx: {
borderTop: '1px solid rgb(230, 230, 230)',
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 100px 0px'
}
}, React.createElement(themeUi.Flex, {
sx: {
alignItems: 'center'
}
}, React.createElement(themeUi.Box, {
mr: 3,
sx: {
flex: 1
}
}, React.createElement(themeUi.Textarea, {
sx: {
fontFamily: 'body',
color: 'input',
variant: 'styles.textarea.transparent'
},
className: 'TextArea--transparent',
placeholder: 'Start typing...',
rows: 1,
autoFocus: true,
value: message,
onKeyDown: this.handleKeyDown,
onChange: this.handleMessageChange
})), React.createElement(themeUi.Box, {
pl: 3
}, React.createElement(themeUi.Button, {
variant: 'primary',
type: 'submit',
disabled: isSending,
onClick: this.handleSendMessage,
sx: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '50%',
height: '36px',
width: '36px',
padding: 0
}
}, React.createElement(SendIcon, {
width: 16,
height: 16,
fill: 'background'
}))))), React.createElement("img", {
src: 'https://papercups.s3.us-east-2.amazonaws.com/papercups-logo.svg',
width: '0',
height: '0'
}));
};
return ChatWindow;
}(React.Component);
var Path = function Path(props) {

@@ -1048,2 +299,26 @@ return React.createElement(framerMotion.motion.path, Object.assign({

var IFRAME_URL = 'https://chat-window.vercel.app';
var setup = function setup(w, handlers) {
var cb = function cb(msg) {
if (msg.origin !== IFRAME_URL) {
return;
}
handlers(msg);
};
if (w.addEventListener) {
w.addEventListener('message', cb);
return function () {
return w.removeEventListener('message', cb);
};
} else {
w.attachEvent('onmessage', cb);
return function () {
return w.detachEvent('message', cb);
};
}
};
var EmbeddableWidget = function EmbeddableWidget(_ref) {

@@ -1060,6 +335,15 @@ var accountId = _ref.accountId,

var _React$useState = React.useState(defaultIsOpen),
var _React$useState = React.useState(false),
isOpen = _React$useState[0],
setIsOpen = _React$useState[1];
var iframeRef = React.useRef(null);
var query = qs.stringify({
accountId: accountId,
title: title,
subtitle: subtitle,
primaryColor: primaryColor,
baseUrl: baseUrl,
greeting: greeting
});
var theme = getThemeConfig({

@@ -1069,2 +353,56 @@ primary: primaryColor

var send = function send(event, payload) {
console.log('Sending from parent:', {
event: event,
payload: payload
});
var el = iframeRef.current;
el.contentWindow.postMessage({
event: event,
payload: payload
}, '*');
};
var handleChatLoaded = function handleChatLoaded() {
if (defaultIsOpen) {
setIsOpen(true);
}
return send('papercups:ping');
};
var sendCustomerUpdate = function sendCustomerUpdate(payload) {
var customerId = payload.customerId;
return send('customer:update', {
customerId: customerId,
metadata: customer
});
};
var handlers = function handlers(msg) {
console.log('Handling in parent:', msg.data);
var _msg$data = msg.data,
event = _msg$data.event,
_msg$data$payload = _msg$data.payload,
payload = _msg$data$payload === void 0 ? {} : _msg$data$payload;
switch (event) {
case 'chat:loaded':
return handleChatLoaded();
case 'conversation:join':
return sendCustomerUpdate(payload);
default:
return null;
}
};
React.useEffect(function () {
var unsubscribe = setup(window, handlers);
return function () {
return unsubscribe();
};
}, []);
var handleToggleOpen = function handleToggleOpen() {

@@ -1076,12 +414,16 @@ return setIsOpen(!isOpen);

theme: theme
}, isOpen && themeUi.jsx(framerMotion.motion.div, {
}, themeUi.jsx(framerMotion.motion.iframe, {
ref: iframeRef,
className: 'Papercups-chatWindowContainer',
initial: {
opacity: 0,
y: 4
animate: isOpen ? 'open' : 'closed',
variants: {
closed: {
opacity: 0,
y: 4
},
open: {
opacity: 1,
y: 0
}
},
animate: {
opacity: 1,
y: 0
},
transition: {

@@ -1091,13 +433,12 @@ duration: 0.2,

},
src: IFRAME_URL + "?" + query,
style: isOpen ? {} : {
bottom: -9999
},
sx: {
border: 'none',
bg: 'background',
variant: 'styles.WidgetContainer'
}
}, themeUi.jsx(ChatWindow, {
title: title,
subtitle: subtitle,
accountId: accountId,
greeting: greeting,
customer: customer,
baseUrl: baseUrl
})), themeUi.jsx(framerMotion.motion.div, {
}, "Loading..."), themeUi.jsx(framerMotion.motion.div, {
className: 'Papercups-toggleButtonContainer',

@@ -1104,0 +445,0 @@ initial: false,

import React from 'react';
import { motion } from 'framer-motion';
import { jsx, Box, Flex, Text, Heading, Textarea, Button, ThemeProvider } from 'theme-ui';
import { Socket } from 'phoenix';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import request from 'superagent';
import { Flex, Button, jsx, ThemeProvider } from 'theme-ui';
import qs from 'query-string';
import tinycolor from 'tinycolor2';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
// A type of promise-like that resolves synchronously and supports only one observer
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator";
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator";
// Asynchronously call a function and send errors to recovery continuation
function _catch(body, recover) {
try {
var result = body();
} catch(e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
var BotIcon = function BotIcon(_ref) {
var width = _ref.width,
height = _ref.height,
fill = _ref.fill,
className = _ref.className;
return jsx("svg", {
viewBox: '64 64 896 896',
focusable: 'false',
className: className,
"data-icon": 'robot',
width: width || '1em',
height: height || '1em',
sx: {
fill: fill || 'black'
}
}, jsx("path", {
d: 'M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z'
}));
};
dayjs.extend(utc);
var formatRelativeTime = function formatRelativeTime(date) {
var ms = dayjs().diff(date, 'second');
var mins = Math.floor(ms / 60);
var hrs = Math.floor(mins / 60);
var days = Math.floor(hrs / 24);
if (ms < 10) {
return 'just now';
} else if (ms < 60) {
return ms + " seconds ago";
} else if (mins <= 60) {
return mins + " minute" + (mins === 1 ? '' : 's') + " ago";
} else if (hrs <= 24) {
return hrs + " hour" + (hrs === 1 ? '' : 's') + " ago";
} else {
return days + " day" + (days === 1 ? '' : 's') + " ago";
}
};
var getAgentIdentifier = function getAgentIdentifier(user) {
if (!user) {
return 'Agent';
}
var display_name = user.display_name,
full_name = user.full_name,
email = user.email;
var _email$split = email.split('@'),
username = _email$split[0];
return display_name || full_name || username || 'Agent';
};
var SenderAvatar = function SenderAvatar(_ref) {
var name = _ref.name,
user = _ref.user,
isBot = _ref.isBot;
var profilePhotoUrl = user && user.profile_photo_url;
if (profilePhotoUrl) {
return React.createElement(Box, {
mr: 2,
style: {
height: 32,
width: 32,
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundImage: "url(" + profilePhotoUrl + ")"
}
});
}
return React.createElement(Flex, {
mr: 2,
sx: {
bg: isBot ? 'gray' : 'primary',
height: 32,
width: 32,
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
color: '#fff'
}
}, isBot ? React.createElement(BotIcon, {
fill: 'background',
height: 16,
width: 16
}) : name.slice(0, 1).toUpperCase());
};
var ChatMessage = function ChatMessage(_ref2) {
var message = _ref2.message,
isMe = _ref2.isMe,
isLastInGroup = _ref2.isLastInGroup,
shouldDisplayTimestamp = _ref2.shouldDisplayTimestamp;
var body = message.body,
created_at = message.created_at,
user = message.user,
type = message.type;
var created = dayjs.utc(created_at);
var timestamp = formatRelativeTime(created);
var isBot = type === 'bot';
var identifer = isBot ? 'Bot' : getAgentIdentifier(user);
if (isMe) {
return React.createElement(Box, {
pr: 0,
pl: 4,
pb: isLastInGroup ? 3 : 2
}, React.createElement(Flex, {
sx: {
justifyContent: 'flex-end'
}
}, React.createElement(Box, {
px: '14px',
py: 2,
sx: {
color: 'background',
bg: 'primary',
borderRadius: 4
}
}, React.createElement(Text, null, body))), shouldDisplayTimestamp && React.createElement(Flex, {
m: 1,
sx: {
justifyContent: 'flex-end'
}
}, React.createElement(Text, {
sx: {
color: 'gray'
}
}, "Sent ", timestamp)));
}
return React.createElement(Box, {
pr: 4,
pl: 0,
pb: isLastInGroup ? 3 : 2
}, React.createElement(Flex, {
sx: {
justifyContent: 'flex-start',
alignItems: 'center'
}
}, React.createElement(SenderAvatar, {
name: identifer,
user: user,
isBot: isBot
}), React.createElement(Box, {
px: '14px',
py: 2,
sx: {
color: 'text',
bg: 'rgb(245, 245, 245)',
borderRadius: 4,
maxWidth: '80%'
}
}, body)), shouldDisplayTimestamp && React.createElement(Flex, {
m: 1,
sx: {
justifyContent: 'flex-start'
}
}, React.createElement(Text, {
sx: {
color: 'gray'
}
}, identifer, " \xB7 Sent ", timestamp)));
};
var SendIcon = function SendIcon(_ref) {
var width = _ref.width,
height = _ref.height,
fill = _ref.fill,
className = _ref.className;
return jsx("svg", {
focusable: 'false',
"aria-hidden": 'true',
height: height || 16,
width: width || 16,
viewBox: '0 0 15 16',
className: className,
sx: {
fill: fill || 'black'
}
}, jsx("g", {
transform: 'translate(-24.000000, -12.000000)'
}, jsx("path", {
d: 'M25.4036262,27.3409362 C24.4176893,27.8509036 23.8195834,27.3951055 24.0683403,26.3201996 L25.0887779,21.910776 C25.2131242,21.3734618 25.7510472,20.8884231 26.3078778,20.8254187 L32.503417,20.1244045 C34.151155,19.9379658 34.1569707,19.6389088 32.503417,19.4549971 L26.3078778,18.7659164 C25.7589338,18.7048617 25.2129433,18.217839 25.0887779,17.6798715 L24.0683403,13.2586546 C23.8198614,12.1820783 24.408944,11.7182276 25.4036262,12.2327184 L38.22304,18.8634497 C39.208977,19.373417 39.2177223,20.1957141 38.22304,20.7102049 L25.4036262,27.3409362 Z'
})));
};
var DEFAULT_BASE_URL = 'https://app.papercups.io';
var getWebsocketUrl = function getWebsocketUrl(baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
var _baseUrl$split = baseUrl.split('://'),
protocol = _baseUrl$split[0],
host = _baseUrl$split[1];
var isHttps = protocol === 'https';
return (isHttps ? 'wss' : 'ws') + "://" + host + "/socket";
};
function now() {
var date = new Date();
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
var EMPTY_METADATA = {};
var createNewCustomer = function createNewCustomer(accountId, metadata, baseUrl) {
if (metadata === void 0) {
metadata = EMPTY_METADATA;
}
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.post(baseUrl + "/api/customers").send({
customer: _extends({}, metadata, {
account_id: accountId,
first_seen: now(),
last_seen: now()
})
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var updateCustomerMetadata = function updateCustomerMetadata(customerId, metadata, baseUrl) {
if (metadata === void 0) {
metadata = EMPTY_METADATA;
}
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.put(baseUrl + "/api/customers/" + customerId + "/metadata").send({
metadata: metadata
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var createNewConversation = function createNewConversation(accountId, customerId, baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.post(baseUrl + "/api/conversations").send({
conversation: {
account_id: accountId,
customer_id: customerId
}
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var fetchCustomerConversations = function fetchCustomerConversations(customerId, accountId, baseUrl) {
if (baseUrl === void 0) {
baseUrl = DEFAULT_BASE_URL;
}
try {
return Promise.resolve(request.get(baseUrl + "/api/conversations/customer").query({
customer_id: customerId,
account_id: accountId
}).then(function (res) {
return res.body.data;
}));
} catch (e) {
return Promise.reject(e);
}
};
var PREFIX = '__PAPERCUPS__';
var get = function get(key) {
var result = localStorage.getItem("" + PREFIX + key);
if (!result) {
return null;
}
try {
return JSON.parse(result);
} catch (e) {
return result;
}
};
var set = function set(key, value) {
localStorage.setItem("" + PREFIX + key, JSON.stringify(value));
};
var getCustomerId = function getCustomerId() {
return get('__CUSTOMER_ID__');
};
var setCustomerId = function setCustomerId(id) {
return set('__CUSTOMER_ID__', id);
};
var ChatWindow = /*#__PURE__*/function (_React$Component) {
_inheritsLoose(ChatWindow, _React$Component);
function ChatWindow() {
var _this;
_this = _React$Component.apply(this, arguments) || this;
_this.scrollToEl = null;
_this.state = {
message: '',
messages: [],
customerId: getCustomerId(),
conversationId: null,
isSending: false
};
_this.getDefaultGreeting = function () {
var greeting = _this.props.greeting;
if (!greeting) {
return [];
}
return [{
type: 'bot',
body: greeting,
created_at: now().toString()
}];
};
_this.fetchLatestConversation = function (customerId) {
try {
if (!customerId) {
_this.setState({
messages: [].concat(_this.getDefaultGreeting())
});
return Promise.resolve();
}
var _this$props = _this.props,
accountId = _this$props.accountId,
baseUrl = _this$props.baseUrl,
metadata = _this$props.customer;
console.log('Fetching conversations for customer:', customerId);
return Promise.resolve(_catch(function () {
return Promise.resolve(fetchCustomerConversations(customerId, accountId, baseUrl)).then(function (conversations) {
console.log('Found existing conversations:', conversations);
if (!conversations || !conversations.length) {
_this.setState({
messages: [].concat(_this.getDefaultGreeting())
});
return;
}
var latest = conversations[0];
var conversationId = latest.id,
_latest$messages = latest.messages,
messages = _latest$messages === void 0 ? [] : _latest$messages;
var formattedMessages = messages.sort(function (a, b) {
return +new Date(a.created_at) - +new Date(b.created_at);
});
_this.setState({
conversationId: conversationId,
messages: [].concat(_this.getDefaultGreeting(), formattedMessages)
});
_this.joinConversationChannel(conversationId, customerId);
return Promise.resolve(_this.updateExistingCustomer(customerId, metadata)).then(function () {});
});
}, function (err) {
console.log('Error fetching conversations!', err);
}));
} catch (e) {
return Promise.reject(e);
}
};
_this.createNewCustomerId = function (accountId) {
try {
var _this$props2 = _this.props,
baseUrl = _this$props2.baseUrl,
metadata = _this$props2.customer;
return Promise.resolve(createNewCustomer(accountId, metadata, baseUrl)).then(function (_ref) {
var customerId = _ref.id;
setCustomerId(customerId);
return customerId;
});
} catch (e) {
return Promise.reject(e);
}
};
_this.updateExistingCustomer = function (customerId, metadata) {
try {
if (!metadata) {
return Promise.resolve();
}
var _temp2 = _catch(function () {
var baseUrl = _this.props.baseUrl;
return Promise.resolve(updateCustomerMetadata(customerId, metadata, baseUrl)).then(function () {});
}, function (err) {
console.log('Error updating customer metadata!', err);
});
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0);
} catch (e) {
return Promise.reject(e);
}
};
_this.initializeNewConversation = function () {
try {
var _this$props3 = _this.props,
accountId = _this$props3.accountId,
baseUrl = _this$props3.baseUrl;
return Promise.resolve(_this.createNewCustomerId(accountId)).then(function (customerId) {
return Promise.resolve(createNewConversation(accountId, customerId, baseUrl)).then(function (_ref2) {
var conversationId = _ref2.id;
_this.setState({
customerId: customerId,
conversationId: conversationId
});
_this.joinConversationChannel(conversationId, customerId);
return {
customerId: customerId,
conversationId: conversationId
};
});
});
} catch (e) {
return Promise.reject(e);
}
};
_this.joinConversationChannel = function (conversationId, customerId) {
if (_this.channel && _this.channel.leave) {
_this.channel.leave();
}
console.log('Joining channel:', conversationId);
_this.channel = _this.socket.channel("conversation:" + conversationId, {
customer_id: customerId
});
_this.channel.on('shout', function (message) {
_this.handleNewMessage(message);
});
_this.channel.join().receive('ok', function (res) {
console.log('Joined successfully!', res);
}).receive('error', function (err) {
console.log('Unable to join!', err);
});
_this.scrollToEl.scrollIntoView();
};
_this.handleNewMessage = function (message) {
_this.setState({
messages: [].concat(_this.state.messages, [message])
}, function () {
_this.scrollToEl.scrollIntoView();
});
};
_this.handleMessageChange = function (e) {
_this.setState({
message: e.target.value
});
};
_this.handleKeyDown = function (e) {
if (e.key === 'Enter') {
_this.handleSendMessage(e);
}
};
_this.handleSendMessage = function (e) {
try {
var _temp5 = function _temp5() {
if (!_this.channel) {
_this.setState({
isSending: false
});
return;
}
_this.channel.push('shout', {
body: message,
customer_id: _this.state.customerId
});
_this.setState({
message: '',
isSending: false
});
};
e && e.preventDefault();
var _this$state = _this.state,
message = _this$state.message,
customerId = _this$state.customerId,
conversationId = _this$state.conversationId,
isSending = _this$state.isSending;
if (isSending || !message || message.trim().length === 0) {
return Promise.resolve();
}
_this.setState({
isSending: true
});
var _temp6 = function () {
if (!customerId || !conversationId) {
return Promise.resolve(_this.initializeNewConversation()).then(function () {});
}
}();
return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6));
} catch (e) {
return Promise.reject(e);
}
};
return _this;
}
var _proto = ChatWindow.prototype;
_proto.componentDidMount = function componentDidMount() {
var websocketUrl = getWebsocketUrl(this.props.baseUrl);
this.socket = new Socket(websocketUrl);
this.socket.connect();
this.fetchLatestConversation(this.state.customerId);
};
_proto.componentWillUnmount = function componentWillUnmount() {
this.channel && this.channel.leave();
};
_proto.render = function render() {
var _this2 = this;
var _this$props4 = this.props,
_this$props4$title = _this$props4.title,
title = _this$props4$title === void 0 ? 'Welcome!' : _this$props4$title,
_this$props4$subtitle = _this$props4.subtitle,
subtitle = _this$props4$subtitle === void 0 ? 'How can we help you?' : _this$props4$subtitle;
var _this$state2 = this.state,
customerId = _this$state2.customerId,
message = _this$state2.message,
_this$state2$messages = _this$state2.messages,
messages = _this$state2$messages === void 0 ? [] : _this$state2$messages,
isSending = _this$state2.isSending;
return React.createElement(Flex, {
sx: {
bg: 'background',
flexDirection: 'column',
height: '100%',
width: '100%'
}
}, React.createElement(Box, {
py: 3,
px: 4,
sx: {
bg: 'primary'
}
}, React.createElement(Heading, {
as: 'h2',
className: 'Papercups-heading',
sx: {
color: 'background',
my: 1
}
}, title), React.createElement(Text, {
sx: {
color: 'offset'
}
}, subtitle)), React.createElement(Box, {
p: 3,
sx: {
flex: 1,
boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset',
overflowY: 'scroll'
}
}, messages.map(function (msg, key) {
var next = messages[key + 1];
var isLastInGroup = next ? msg.customer_id !== next.customer_id : true;
var shouldDisplayTimestamp = key === messages.length - 1;
var isMe = msg.customer_id === customerId;
return React.createElement(motion.div, {
key: key,
initial: {
opacity: 0,
x: isMe ? 2 : -2
},
animate: {
opacity: 1,
x: 0
},
transition: {
duration: 0.2,
ease: 'easeIn'
}
}, React.createElement(ChatMessage, {
key: key,
message: msg,
isMe: isMe,
isLastInGroup: isLastInGroup,
shouldDisplayTimestamp: shouldDisplayTimestamp
}));
}), React.createElement("div", {
ref: function ref(el) {
return _this2.scrollToEl = el;
}
})), React.createElement(Box, {
p: 2,
sx: {
borderTop: '1px solid rgb(230, 230, 230)',
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 100px 0px'
}
}, React.createElement(Flex, {
sx: {
alignItems: 'center'
}
}, React.createElement(Box, {
mr: 3,
sx: {
flex: 1
}
}, React.createElement(Textarea, {
sx: {
fontFamily: 'body',
color: 'input',
variant: 'styles.textarea.transparent'
},
className: 'TextArea--transparent',
placeholder: 'Start typing...',
rows: 1,
autoFocus: true,
value: message,
onKeyDown: this.handleKeyDown,
onChange: this.handleMessageChange
})), React.createElement(Box, {
pl: 3
}, React.createElement(Button, {
variant: 'primary',
type: 'submit',
disabled: isSending,
onClick: this.handleSendMessage,
sx: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '50%',
height: '36px',
width: '36px',
padding: 0
}
}, React.createElement(SendIcon, {
width: 16,
height: 16,
fill: 'background'
}))))), React.createElement("img", {
src: 'https://papercups.s3.us-east-2.amazonaws.com/papercups-logo.svg',
width: '0',
height: '0'
}));
};
return ChatWindow;
}(React.Component);
var Path = function Path(props) {

@@ -1045,2 +296,26 @@ return React.createElement(motion.path, Object.assign({

var IFRAME_URL = 'https://chat-window.vercel.app';
var setup = function setup(w, handlers) {
var cb = function cb(msg) {
if (msg.origin !== IFRAME_URL) {
return;
}
handlers(msg);
};
if (w.addEventListener) {
w.addEventListener('message', cb);
return function () {
return w.removeEventListener('message', cb);
};
} else {
w.attachEvent('onmessage', cb);
return function () {
return w.detachEvent('message', cb);
};
}
};
var EmbeddableWidget = function EmbeddableWidget(_ref) {

@@ -1057,6 +332,15 @@ var accountId = _ref.accountId,

var _React$useState = React.useState(defaultIsOpen),
var _React$useState = React.useState(false),
isOpen = _React$useState[0],
setIsOpen = _React$useState[1];
var iframeRef = React.useRef(null);
var query = qs.stringify({
accountId: accountId,
title: title,
subtitle: subtitle,
primaryColor: primaryColor,
baseUrl: baseUrl,
greeting: greeting
});
var theme = getThemeConfig({

@@ -1066,2 +350,56 @@ primary: primaryColor

var send = function send(event, payload) {
console.log('Sending from parent:', {
event: event,
payload: payload
});
var el = iframeRef.current;
el.contentWindow.postMessage({
event: event,
payload: payload
}, '*');
};
var handleChatLoaded = function handleChatLoaded() {
if (defaultIsOpen) {
setIsOpen(true);
}
return send('papercups:ping');
};
var sendCustomerUpdate = function sendCustomerUpdate(payload) {
var customerId = payload.customerId;
return send('customer:update', {
customerId: customerId,
metadata: customer
});
};
var handlers = function handlers(msg) {
console.log('Handling in parent:', msg.data);
var _msg$data = msg.data,
event = _msg$data.event,
_msg$data$payload = _msg$data.payload,
payload = _msg$data$payload === void 0 ? {} : _msg$data$payload;
switch (event) {
case 'chat:loaded':
return handleChatLoaded();
case 'conversation:join':
return sendCustomerUpdate(payload);
default:
return null;
}
};
React.useEffect(function () {
var unsubscribe = setup(window, handlers);
return function () {
return unsubscribe();
};
}, []);
var handleToggleOpen = function handleToggleOpen() {

@@ -1073,12 +411,16 @@ return setIsOpen(!isOpen);

theme: theme
}, isOpen && jsx(motion.div, {
}, jsx(motion.iframe, {
ref: iframeRef,
className: 'Papercups-chatWindowContainer',
initial: {
opacity: 0,
y: 4
animate: isOpen ? 'open' : 'closed',
variants: {
closed: {
opacity: 0,
y: 4
},
open: {
opacity: 1,
y: 0
}
},
animate: {
opacity: 1,
y: 0
},
transition: {

@@ -1088,13 +430,12 @@ duration: 0.2,

},
src: IFRAME_URL + "?" + query,
style: isOpen ? {} : {
bottom: -9999
},
sx: {
border: 'none',
bg: 'background',
variant: 'styles.WidgetContainer'
}
}, jsx(ChatWindow, {
title: title,
subtitle: subtitle,
accountId: accountId,
greeting: greeting,
customer: customer,
baseUrl: baseUrl
})), jsx(motion.div, {
}, "Loading..."), jsx(motion.div, {
className: 'Papercups-toggleButtonContainer',

@@ -1101,0 +442,0 @@ initial: false,

3

package.json
{
"name": "@papercups-io/chat-widget",
"version": "1.0.15",
"version": "1.0.16-beta.0",
"description": "Papercups chat widget",

@@ -71,2 +71,3 @@ "author": "reichert621",

"phoenix": "^1.5.3",
"query-string": "^6.13.1",
"superagent": "^5.3.1",

@@ -73,0 +74,0 @@ "theme-ui": "^0.3.1",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc