intercom-react
Version:
An Intercom component for React.
336 lines (314 loc) • 12.6 kB
JavaScript
import { PureComponent, createElement, Fragment, createRef, Component } from 'react';
import { createPortal } from 'react-dom';
import { bind } from 'lodash-decorators';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function classNames(...classes) {
return classes.filter((className) => className).join(' ');
}
var getIntercomFromFrame = (frame) => frame.contentWindow.Intercom;
var objectEqual = (object1, object2) => JSON.stringify(object1) === JSON.stringify(object2);
function injectCustomStyles(frame, styles) {
const { contentWindow } = frame;
const node = document.createElement('style');
node.innerHTML = styles;
contentWindow.document.head.appendChild(node);
}
class Portal extends PureComponent {
render() {
const { children } = this.props;
return createPortal(children, document.body);
}
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var ImportIsolatedRemote = "ImportIsolatedRemote_ImportIsolatedRemote__2bxiR";
var css = ".ImportIsolatedRemote_ImportIsolatedRemote__2bxiR {\n width: 0;\n height: 0;\n border: none; }\n";
styleInject(css);
class ImportIsolatedRemote$2 extends PureComponent {
constructor() {
super(...arguments);
this.frameNode = createRef();
this.scriptNode = null;
}
componentDidMount() {
const { current: frame } = this.frameNode;
const { source, onImported } = this.props;
if (!frame || !frame.contentWindow) {
return;
}
const { contentWindow } = frame;
const script = document.createElement('script');
script.src = source;
this.scriptNode = script;
function loadScript() {
contentWindow.document.body.appendChild(script);
script.onload = () => onImported(frame);
}
if (frame.contentDocument.readyState === 'uninitialized') {
frame.onload = loadScript;
return;
}
loadScript();
}
componentWillUnmount() {
const { scriptNode, frameNode: { current: frame }, } = this;
if (frame) {
frame.onload = null;
}
if (scriptNode) {
scriptNode.onload = null;
}
}
render() {
const { title } = this.props;
return (createElement(Portal, null,
createElement("iframe", { className: ImportIsolatedRemote, title: title, ref: this.frameNode })));
}
}
const LAUNCHER_SIZE_PIXELS = 60;
const LAUNCHER_MARGIN_PIXELS = 20;
class BorderlessFrameObserver extends Component {
constructor() {
super(...arguments);
this.observer = null;
}
componentWillMount() {
const { frame } = this.props;
this.observeNode(frame.contentWindow.document.body, this.handleBodyMutation, {
childList: true,
});
this.injectCustomGradientStyles();
}
componentWillUnmount() {
this.cleanObserver();
}
render() {
return null;
}
injectCustomGradientStyles() {
const { frame } = this.props;
injectCustomStyles(frame, `
.intercom-gradient {
width: 100% !important;
height: 100% !important;
}
`);
}
observeNode(nodeToObserve, onMutation, options) {
this.cleanObserver();
const observer = new MutationObserver(onMutation);
observer.observe(nodeToObserve, options);
this.observer = observer;
return observer;
}
cleanObserver() {
const { observer } = this;
if (observer) {
observer.disconnect();
}
}
handleBodyMutation([{ target: body }]) {
const intercomAppNode = body.querySelector('.intercom-app');
this.observeNode(intercomAppNode, this.handleIntercomAppMutation, {
attributes: true,
subtree: true,
});
}
handleIntercomAppMutation(mutations) {
const { launcher, onSizesUpdate } = this.props;
for (const { target } of mutations) {
const node = target;
if (!node.classList.contains('intercom-borderless-frame') &&
!node.classList.contains('intercom-notifications-frame')) {
continue;
}
const { style: { height }, offsetWidth, } = node;
const finalWidth = launcher
? offsetWidth + LAUNCHER_MARGIN_PIXELS
: offsetWidth;
let finalHeight = height || '0px';
if (height && launcher) {
finalHeight = addMarginToPixels(height, LAUNCHER_SIZE_PIXELS + LAUNCHER_MARGIN_PIXELS * 2);
}
onSizesUpdate({
width: `${finalWidth}px`,
height: finalHeight,
});
return;
}
}
}
__decorate([
bind(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array]),
__metadata("design:returntype", void 0)
], BorderlessFrameObserver.prototype, "handleBodyMutation", null);
__decorate([
bind(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array]),
__metadata("design:returntype", void 0)
], BorderlessFrameObserver.prototype, "handleIntercomAppMutation", null);
function addMarginToPixels(pixelsString, margin) {
const pixels = Number(pixelsString.replace('px', ''));
return `${pixels + margin}px`;
}
var Intercom = "Intercom_Intercom__3Amxb";
var IntercomHasLauncher = "Intercom_IntercomHasLauncher__2YqCZ";
var IntercomAnimating = "Intercom_IntercomAnimating__3MvHf";
var IntercomOpen = "Intercom_IntercomOpen__3SD9A";
var css$1 = ".Intercom_Intercom__3Amxb {\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 999;\n width: 0;\n height: 0;\n background: transparent;\n border: none; }\n\n.Intercom_IntercomHasLauncher__2YqCZ {\n width: 90px;\n height: 90px; }\n\n.Intercom_IntercomAnimating__3MvHf {\n pointer-events: none; }\n\n.Intercom_IntercomOpen__3SD9A {\n width: 100%;\n max-width: 451px;\n height: calc(100% - 40px);\n min-height: 250px;\n max-height: 704px; }\n @media all and (max-width: 451px) {\n .Intercom_IntercomOpen__3SD9A {\n height: 100%;\n max-height: none; } }\n";
styleInject(css$1);
const ANIMATION_DURATION = 300;
class Intercom$2 extends PureComponent {
constructor() {
super(...arguments);
this.state = {
frame: null,
};
}
componentWillReceiveProps({ open: nextOpen, user: nextUser }) {
const { user } = this.props;
if (nextOpen) {
this.getIntercom()('show');
}
if (nextUser && !objectEqual(user || {}, nextUser)) {
this.getIntercom()('update', nextUser);
}
}
componentWillUnmount() {
this.getIntercom()('shutdown');
}
render() {
const { appId, launcher } = this.props;
const { frame } = this.state;
const importUrl = `https://widget.intercom.io/widget/${appId}`;
const borderlessFrameObserver = frame && (createElement(BorderlessFrameObserver, { frame: frame, onSizesUpdate: this.handleBorderlessFrameSizesUpdate, launcher: launcher }));
return (createElement(Fragment, null,
createElement(ImportIsolatedRemote$2, { title: "intercom", source: importUrl, onImported: this.initializeIntercom }),
borderlessFrameObserver));
}
updateState({ open = false, animating = false, borderlessFrameSizes = null, }) {
const { launcher } = this.props;
const { frame } = this.state;
if (!frame) {
return;
}
if (borderlessFrameSizes) {
const { width, height } = borderlessFrameSizes;
frame.setAttribute('style', `width: ${width}; height: ${height};`);
}
else {
frame.removeAttribute('style');
}
const className = classNames(Intercom, launcher && IntercomHasLauncher, open && IntercomOpen, animating && IntercomAnimating);
frame.setAttribute('class', className);
}
initializeIntercom(frame) {
const { open, onOpen, onClose, appId, onUnreadCountChange, user, onInitialization, launcher, } = this.props;
const intercom = getIntercomFromFrame(frame);
this.setState({ frame });
intercom('boot', Object.assign({ app_id: appId }, user, { hide_default_launcher: !launcher }));
intercom('onShow', () => {
this.updateState({ open: true, animating: false });
if (onOpen) {
onOpen();
}
});
intercom('onHide', () => {
this.updateState({ open: true, animating: true });
setTimeout(() => this.updateState({ open: false, animating: false }), ANIMATION_DURATION);
if (onClose) {
onClose();
}
});
if (onUnreadCountChange) {
intercom('onUnreadCountChange', onUnreadCountChange);
}
if (open) {
intercom('show');
}
else {
this.updateState({ open: false, animating: false });
}
this.injectCustomLauncherStyles();
if (onInitialization) {
onInitialization(intercom);
}
}
getIntercom() {
const { frame } = this.state;
if (!frame) {
return () => { };
}
return getIntercomFromFrame(frame);
}
injectCustomLauncherStyles() {
const { frame } = this.state;
injectCustomStyles(frame, `
.intercom-launcher-frame-shadow {
box-shadow: none !important;
}
`);
}
handleBorderlessFrameSizesUpdate(borderlessFrameSizes) {
this.updateState({ borderlessFrameSizes });
}
}
Intercom$2.defaultProps = {
launcher: true,
};
__decorate([
bind(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [HTMLIFrameElement]),
__metadata("design:returntype", void 0)
], Intercom$2.prototype, "initializeIntercom", null);
__decorate([
bind(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], Intercom$2.prototype, "handleBorderlessFrameSizesUpdate", null);
export default Intercom$2;