import {Component} from 'react'; import * as qs from 'query-string'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; import {t} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; import {IntegrationProvider, IntegrationWithConfig, Organization} from 'sentry/types'; import {trackIntegrationAnalytics} from 'sentry/utils/integrationUtil'; type Props = { children: ( openDialog: (urlParams?: {[key: string]: string}) => void ) => React.ReactNode; onInstall: (data: IntegrationWithConfig) => void; organization: Organization; provider: IntegrationProvider; account?: string; // for analytics analyticsParams?: { already_installed: boolean; view: | 'integrations_directory_integration_detail' | 'integrations_directory' | 'onboarding' | 'project_creation'; }; modalParams?: {[key: string]: string}; }; export default class AddIntegration extends Component { componentDidMount() { window.addEventListener('message', this.didReceiveMessage); } componentWillUnmount() { window.removeEventListener('message', this.didReceiveMessage); this.dialog?.close(); } dialog: Window | null = null; computeCenteredWindow(width: number, height: number) { // Taken from: https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen const screenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX; const screenTop = window.screenTop !== undefined ? window.screenTop : window.screenY; const innerWidth = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; const innerHeight = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; const left = innerWidth / 2 - width / 2 + screenLeft; const top = innerHeight / 2 - height / 2 + screenTop; return {left, top}; } openDialog = (urlParams?: {[key: string]: string}) => { const {account, analyticsParams, modalParams, organization, provider} = this.props; trackIntegrationAnalytics('integrations.installation_start', { integration: provider.key, integration_type: 'first_party', organization, ...analyticsParams, }); const name = 'sentryAddIntegration'; const {url, width, height} = provider.setupDialog; const {left, top} = this.computeCenteredWindow(width, height); let query: {[key: string]: string} = {...urlParams}; if (account) { query.account = account; } if (modalParams) { query = {...query, ...modalParams}; } const installUrl = `${url}?${qs.stringify(query)}`; const opts = `scrollbars=yes,width=${width},height=${height},top=${top},left=${left}`; this.dialog = window.open(installUrl, name, opts); this.dialog && this.dialog.focus(); }; didReceiveMessage = (message: MessageEvent) => { const {analyticsParams, onInstall, organization, provider} = this.props; const validOrigins = [ ConfigStore.get('links').sentryUrl, ConfigStore.get('links').organizationUrl, document.location.origin, ]; if (!validOrigins.includes(message.origin)) { return; } if (message.source !== this.dialog) { return; } const {success, data} = message.data; this.dialog = null; if (!success) { addErrorMessage(data?.error ?? t('An unknown error occurred')); return; } if (!data) { return; } trackIntegrationAnalytics('integrations.installation_complete', { integration: provider.key, integration_type: 'first_party', organization, ...analyticsParams, }); addSuccessMessage(t('%s added', provider.name)); onInstall(data); }; render() { const {children} = this.props; return children(this.openDialog); } }