withDomainRedirect.tsx 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import type {RouteComponent, RouteComponentProps} from 'react-router';
  2. import {formatPattern} from 'react-router';
  3. import trimEnd from 'lodash/trimEnd';
  4. import trimStart from 'lodash/trimStart';
  5. import Redirect from 'sentry/components/redirect';
  6. import ConfigStore from 'sentry/stores/configStore';
  7. import recreateRoute from 'sentry/utils/recreateRoute';
  8. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  9. import useOrganization from './useOrganization';
  10. /**
  11. * withDomainRedirect is a higher-order component (HOC) meant to be used with <Route /> components within
  12. * static/app/routes.tsx whose route paths contains the :orgId parameter at least once.
  13. * For example:
  14. *
  15. * <Route
  16. * path="/organizations/:orgId/issues/(searches/:searchId/)"
  17. * component={withDomainRedirect(errorHandler(IssueListContainer))}
  18. * / >
  19. *
  20. * withDomainRedirect checks if the associated route path is accessed within a customer domain (e.g. orgslug.sentry.io),
  21. * then redirect the browser to a route path with /organization/:orgId/ and /:orgId/ omitted (in that order).
  22. * For example, redirect /organizations/:orgId/issues/(searches/:searchId/) to /issues/(searches/:searchId/)
  23. *
  24. * If a redirect should occur, then a Sentry event will be emitted.
  25. *
  26. * If either a customer domain is not being used, or if :orgId is not present in the route path, then WrappedComponent
  27. * is rendered.
  28. */
  29. function withDomainRedirect<P extends RouteComponentProps<{}, {}>>(
  30. WrappedComponent: RouteComponent
  31. ) {
  32. return function WithDomainRedirectWrapper(props: P) {
  33. const {customerDomain, links, features} = ConfigStore.getState();
  34. const {sentryUrl} = links;
  35. const currentOrganization = useOrganization({allowNull: true});
  36. if (customerDomain) {
  37. // Customer domain is being used on a route that has an :orgId parameter.
  38. const redirectPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
  39. const redirectURL = `${trimEnd(sentryUrl, '/')}/${trimStart(redirectPath, '/')}`;
  40. // If we have domain information, but the subdomain and slug are different
  41. // redirect to the slug path and let django decide what happens next.
  42. if (
  43. currentOrganization &&
  44. customerDomain.subdomain &&
  45. (currentOrganization.slug !== customerDomain.subdomain ||
  46. !features.has('system:multi-region'))
  47. ) {
  48. window.location.replace(redirectURL);
  49. return null;
  50. }
  51. const {params, routes} = props;
  52. // Regenerate the full route with the :orgId parameter omitted.
  53. const newParams = {...params};
  54. Object.keys(params).forEach(param => {
  55. newParams[param] = `:${param}`;
  56. });
  57. const fullRoute = recreateRoute('', {routes, params: newParams});
  58. const orglessSlugRoute = normalizeUrl(fullRoute, {forceCustomerDomain: true});
  59. if (orglessSlugRoute === fullRoute) {
  60. // :orgId is not present in the route, so we do not need to perform a redirect here.
  61. return <WrappedComponent {...props} />;
  62. }
  63. const orglessRedirectPath = formatPattern(orglessSlugRoute, params);
  64. const redirectOrgURL = `/${trimStart(orglessRedirectPath, '/')}${
  65. window.location.search
  66. }${window.location.hash}`;
  67. // Redirect to a route path with :orgId omitted.
  68. return <Redirect to={redirectOrgURL} router={props.router} />;
  69. }
  70. return <WrappedComponent {...props} />;
  71. };
  72. }
  73. export default withDomainRedirect;