root.jsx 14.4 KB
Newer Older
1 2
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
3

4
import $ from 'jquery';
5
require('perfect-scrollbar/jquery')($);
6

7
import PropTypes from 'prop-types';
8 9
import React from 'react';
import FastClick from 'fastclick';
10
import {Route, Switch, Redirect} from 'react-router-dom';
11
import {setUrl} from 'mattermost-redux/actions/general';
12
import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
13
import {getConfig} from 'mattermost-redux/selectors/entities/general';
14

15
import * as UserAgent from 'utils/user_agent.jsx';
16
import {EmojiIndicesByAlias} from 'utils/emoji.jsx';
17
import {trackLoadTime} from 'actions/diagnostics_actions.jsx';
18
import * as GlobalActions from 'actions/global_actions.jsx';
19
import BrowserStore from 'stores/browser_store.jsx';
20
import {loadRecentlyUsedCustomEmojis} from 'actions/emoji_actions.jsx';
21 22
import * as I18n from 'i18n/i18n.jsx';
import {initializePlugins} from 'plugins';
Joram Wilander's avatar
Joram Wilander committed
23
import 'plugins/export.js';
24
import Constants, {StoragePrefixes} from 'utils/constants.jsx';
25
import {HFTRoute, LoggedInHFTRoute} from 'components/header_footer_template_route';
26
import IntlProvider from 'components/intl_provider';
27 28 29 30 31 32 33 34 35
import NeedsTeam from 'components/needs_team';
import {makeAsyncComponent} from 'components/async_load';
import loadErrorPage from 'bundle-loader?lazy!components/error_page';
import loadLoginController from 'bundle-loader?lazy!components/login/login_controller';
import loadAdminConsole from 'bundle-loader?lazy!components/admin_console';
import loadLoggedIn from 'bundle-loader?lazy!components/logged_in';
import loadPasswordResetSendLink from 'bundle-loader?lazy!components/password_reset_send_link';
import loadPasswordResetForm from 'bundle-loader?lazy!components/password_reset_form';
import loadSignupController from 'bundle-loader?lazy!components/signup/signup_controller';
36
import loadSignupEmail from 'bundle-loader?lazy!components/signup/signup_email';
37
import loadTermsOfService from 'bundle-loader?lazy!components/terms_of_service';
38 39
import loadShouldVerifyEmail from 'bundle-loader?lazy!components/should_verify_email';
import loadDoVerifyEmail from 'bundle-loader?lazy!components/do_verify_email';
40
import loadClaimController from 'bundle-loader?lazy!components/claim';
41
import loadHelpController from 'bundle-loader?lazy!components/help/help_controller';
42
import loadGetIosApp from 'bundle-loader?lazy!components/get_ios_app';
43
import loadGetAndroidApp from 'bundle-loader?lazy!components/get_android_app';
44 45
import loadSelectTeam from 'bundle-loader?lazy!components/select_team';
import loadAuthorize from 'bundle-loader?lazy!components/authorize';
46
import loadCreateTeam from 'bundle-loader?lazy!components/create_team';
47 48
import loadMfa from 'bundle-loader?lazy!components/mfa/mfa_controller';
import store from 'stores/redux_store.jsx';
49
import {getSiteURL} from 'utils/url.jsx';
50 51 52

const CreateTeam = makeAsyncComponent(loadCreateTeam);
const ErrorPage = makeAsyncComponent(loadErrorPage);
53
const TermsOfService = makeAsyncComponent(loadTermsOfService);
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
const LoginController = makeAsyncComponent(loadLoginController);
const AdminConsole = makeAsyncComponent(loadAdminConsole);
const LoggedIn = makeAsyncComponent(loadLoggedIn);
const PasswordResetSendLink = makeAsyncComponent(loadPasswordResetSendLink);
const PasswordResetForm = makeAsyncComponent(loadPasswordResetForm);
const SignupController = makeAsyncComponent(loadSignupController);
const SignupEmail = makeAsyncComponent(loadSignupEmail);
const ShouldVerifyEmail = makeAsyncComponent(loadShouldVerifyEmail);
const DoVerifyEmail = makeAsyncComponent(loadDoVerifyEmail);
const ClaimController = makeAsyncComponent(loadClaimController);
const HelpController = makeAsyncComponent(loadHelpController);
const GetIosApp = makeAsyncComponent(loadGetIosApp);
const GetAndroidApp = makeAsyncComponent(loadGetAndroidApp);
const SelectTeam = makeAsyncComponent(loadSelectTeam);
const Authorize = makeAsyncComponent(loadAuthorize);
const Mfa = makeAsyncComponent(loadMfa);

const LoggedInRoute = ({component: Component, ...rest}) => (
    <Route
        {...rest}
        render={(props) => (
            <LoggedIn {...props}>
                <Component {...props}/>
            </LoggedIn>
78
        )}
79 80
    />
);
81

82
export default class Root extends React.Component {
83 84
    static propTypes = {
        diagnosticsEnabled: PropTypes.bool,
85
        diagnosticId: PropTypes.string,
86
        noAccounts: PropTypes.bool,
87
        showTermsOfService: PropTypes.bool,
88 89 90
        actions: PropTypes.shape({
            loadMeAndConfig: PropTypes.func.isRequired,
        }).isRequired,
91 92
    }

93 94
    constructor(props) {
        super(props);
enahum's avatar
enahum committed
95

96
        // Redux
97 98
        setUrl(getSiteURL());

99
        setSystemEmojis(EmojiIndicesByAlias);
enahum's avatar
enahum committed
100

101 102 103
        // Force logout of all tabs if one tab is logged out
        $(window).bind('storage', (e) => {
            // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
104
            if (e.originalEvent.key === StoragePrefixes.LOGOUT && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
105 106 107 108 109 110
                // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
                if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
                    return;
                }

                console.log('detected logout from a different tab'); //eslint-disable-line no-console
111
                GlobalActions.emitUserLoggedOutEvent('/', false, false);
112 113
            }

114
            if (e.originalEvent.key === StoragePrefixes.LOGIN && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
115 116 117 118 119 120 121 122 123 124
                // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
                if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
                    return;
                }

                console.log('detected login from a different tab'); //eslint-disable-line no-console
                location.reload();
            }
        });

125 126 127 128 129 130 131 132 133 134 135
        // Prevent drag and drop files from navigating away from the app
        document.addEventListener('drop', (e) => {
            e.preventDefault();
            e.stopPropagation();
        });

        document.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
        });

enahum's avatar
enahum committed
136 137
        // Fastclick
        FastClick.attach(document.body);
138

139 140 141 142 143
        this.state = {
            configLoaded: false,
        };
    }

144
    onConfigLoaded = () => {
145
        const segmentKey = Constants.DIAGNOSTICS_SEGMENT_KEY;
146
        const diagnosticId = this.props.diagnosticId;
147 148

        /*eslint-disable */
149
        if (segmentKey != null && segmentKey !== '' && this.props.diagnosticsEnabled) {
150
            !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(...args){var e=Array.prototype.slice.call(args);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol ? "https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
151 152
                analytics.load(segmentKey);

153 154 155 156 157 158 159 160 161 162 163 164 165 166
                analytics.identify(diagnosticId, {}, {
                    context: {
                        ip: '0.0.0.0',
                    },
                    page: {
                        path: '',
                        referrer: '',
                        search: '',
                        title: '',
                        url: '',
                    },
                    anonymousId: '00000000000000000000000000',
                });

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
                analytics.page('ApplicationLoaded', {
                        path: '',
                        referrer: '',
                        search: '',
                        title: '',
                        url: '',
                    },
                    {
                        context: {
                            ip: '0.0.0.0'
                        },
                        anonymousId: '00000000000000000000000000'
                    });
            }}();
        }
        /*eslint-enable */

        const afterIntl = () => {
            initializePlugins();
186

187 188 189 190
            if (this.props.location.pathname === '/' && this.props.noAccounts) {
                this.props.history.push('/signup_user_complete');
            }

191 192 193 194 195 196 197
            this.setState({configLoaded: true});
        };
        if (global.Intl) {
            afterIntl();
        } else {
            I18n.safariFix(afterIntl);
        }
198 199

        loadRecentlyUsedCustomEmojis()(store.dispatch, store.getState);
200 201 202 203

        const iosDownloadLink = getConfig(store.getState()).IosAppDownloadLink;
        const androidDownloadLink = getConfig(store.getState()).AndroidAppDownloadLink;

204 205
        const toResetPasswordScreen = this.props.location.pathname === '/reset_password_complete';

206 207 208
        // Remove any redirects - the mobile apps do no work with the instance
        // XXX: Should we make this a proper option/toggle somewhere.
        if (false) {
209
        // redirect to the mobile landing page if the user hasn't seen it before
210
        if (iosDownloadLink && UserAgent.isIosWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
211
            this.props.history.push('/get_ios_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
212
            BrowserStore.setLandingPageSeen(true);
213
        } else if (androidDownloadLink && UserAgent.isAndroidWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
214
            this.props.history.push('/get_android_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
215 216
            BrowserStore.setLandingPageSeen(true);
        }
217
        }
218
    }
219

220
    redirectIfNecessary = (props) => {
221
        if (props.location.pathname === '/') {
222
            if (this.props.noAccounts) {
223
                this.props.history.push('/signup_user_complete');
224 225
            } else if (props.showTermsOfService) {
                this.props.history.push('/terms_of_service');
226
            }
227 228
        }
    }
229

230
    UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
231 232
        this.redirectIfNecessary(newProps);
    }
233

enahum's avatar
enahum committed
234
    componentDidMount() {
235
        this.props.actions.loadMeAndConfig().then((response) => {
236
            if (this.props.location.pathname === '/' && response[2] && response[2].data) {
237 238
                GlobalActions.redirectUserToDefaultTeam();
            }
239 240
            this.onConfigLoaded();
        });
241
        trackLoadTime();
242
    }
243

244
    componentWillUnmount() {
245
        $(window).unbind('storage');
246
    }
247

248
    render() {
249
        if (!this.state.configLoaded) {
250 251 252 253
            return <div/>;
        }

        return (
254
            <IntlProvider>
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
                <Switch>
                    <Route
                        path={'/error'}
                        component={ErrorPage}
                    />
                    <HFTRoute
                        path={'/login'}
                        component={LoginController}
                    />
                    <HFTRoute
                        path={'/reset_password'}
                        component={PasswordResetSendLink}
                    />
                    <HFTRoute
                        path={'/reset_password_complete'}
                        component={PasswordResetForm}
                    />
                    <HFTRoute
                        path={'/signup_user_complete'}
                        component={SignupController}
                    />
                    <HFTRoute
                        path={'/signup_email'}
                        component={SignupEmail}
                    />
                    <HFTRoute
                        path={'/should_verify_email'}
                        component={ShouldVerifyEmail}
                    />
                    <HFTRoute
                        path={'/do_verify_email'}
                        component={DoVerifyEmail}
                    />
                    <HFTRoute
                        path={'/claim'}
                        component={ClaimController}
                    />
                    <HFTRoute
                        path={'/help'}
                        component={HelpController}
                    />
296
                    <LoggedInRoute
297 298 299
                        path={'/terms_of_service'}
                        component={TermsOfService}
                    />
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
                    <Route
                        path={'/get_ios_app'}
                        component={GetIosApp}
                    />
                    <Route
                        path={'/get_android_app'}
                        component={GetAndroidApp}
                    />
                    <LoggedInRoute
                        path={'/admin_console'}
                        component={AdminConsole}
                    />
                    <LoggedInHFTRoute
                        path={'/select_team'}
                        component={SelectTeam}
                    />
                    <LoggedInHFTRoute
                        path={'/oauth/authorize'}
                        component={Authorize}
                    />
                    <LoggedInHFTRoute
                        path={'/create_team'}
                        component={CreateTeam}
                    />
                    <LoggedInRoute
                        path={'/mfa'}
                        component={Mfa}
                    />
                    <LoggedInRoute
                        path={'/:team'}
                        component={NeedsTeam}
                    />
332 333 334 335 336 337
                    <Redirect
                        to={{
                            ...this.props.location,
                            pathname: '/login',
                        }}
                    />
338
                </Switch>
339 340 341 342
            </IntlProvider>
        );
    }
}