import {RouteComponent, RouteComponentProps} from 'react-router'; import {Location, LocationDescriptor} from 'history'; import trimEnd from 'lodash/trimEnd'; import trimStart from 'lodash/trimStart'; const NORMALIZE_PATTERNS: Array<[pattern: RegExp, replacement: string]> = [ // /organizations/slug/section, but not /organizations/new [/\/?organizations\/(?!new)[^\/]+\/(.*)/, '/$1'], // For /settings/:orgId/ -> /settings/organization/ [/\/?settings\/[^\/]+\/?$/, '/settings/organization/'], // Move /settings/:orgId/:section -> /settings/:section // but not /settings/organization or /settings/projects which is a new URL [/\/?settings\/(?!projects)(?!account)[^\/]+\/(.*)/, '/settings/$1'], [/\/?join-request\/[^\/]+\/?.*/, '/join-request/'], [/\/?onboarding\/[^\/]+\/(.*)/, '/onboarding/$1'], [/\/?[^\/]+\/([^\/]+)\/getting-started\/(.*)/, '/getting-started/$1/$2'], ]; type LocationTarget = ((location: Location) => LocationDescriptor) | LocationDescriptor; /** * Normalize a URL for customer domains based on the current route state */ export function normalizeUrl(path: string): string; export function normalizeUrl(path: LocationDescriptor): LocationDescriptor; export function normalizeUrl(path: LocationTarget, location?: Location): LocationTarget; export function normalizeUrl(path: LocationTarget, location?: Location): LocationTarget { if (!window.__initialData?.customerDomain) { return path; } let resolved: LocationDescriptor; if (typeof path === 'function') { if (!location) { throw new Error('Cannot resolve function URL without a location'); } resolved = path(location); } else { resolved = path; } if (typeof resolved === 'string') { for (const patternData of NORMALIZE_PATTERNS) { resolved = resolved.replace(patternData[0], patternData[1]); if (resolved !== path) { return resolved; } } return resolved; } if (!resolved.pathname) { return resolved; } for (const patternData of NORMALIZE_PATTERNS) { const replacement = resolved.pathname.replace(patternData[0], patternData[1]); if (replacement !== resolved.pathname) { return {...resolved, pathname: replacement}; } } return resolved; } /** * withDomainRequired is a higher-order component (HOC) meant to be used with components within * static/app/routes.tsx whose route paths do not contain the :orgId parameter. * For example: * * * withDomainRequired ensures that the route path is only accessed whenever a customer domain is used. * For example: orgslug.sentry.io * * The side-effect that this HOC provides is that it'll redirect the browser to sentryUrl (from window.__initialData.links) * whenever one of the following conditions are not satisfied: * - window.__initialData.customerDomain is present. * - window.__initialData.features contains organizations:customer-domains feature. * * If both conditions above are satisfied, then WrappedComponent will be rendered with orgId included in the route * params prop. * * Whenever https://orgslug.sentry.io/ is accessed in the browser, then both conditions above will be satisfied. */ function withDomainRequired

>( WrappedComponent: RouteComponent ) { return function withDomainRequiredWrapper(props: P) { const {params} = props; const {features, customerDomain} = window.__initialData; const {sentryUrl} = window.__initialData.links; const hasCustomerDomain = (features as unknown as string[]).includes( 'organizations:customer-domains' ); if (!customerDomain || !hasCustomerDomain) { // This route should only be accessed if a customer domain is used. // We redirect the user to the sentryUrl. const redirectPath = `${window.location.pathname}${window.location.search}${window.location.hash}`; const redirectURL = `${trimEnd(sentryUrl, '/')}/${trimStart(redirectPath, '/')}`; window.location.replace(redirectURL); return null; } const newParams = { ...params, orgId: customerDomain.subdomain, }; return ; }; } export default withDomainRequired;