withDomainRedirect.tsx 3.4 KB

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