index.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import type {ResponseMeta} from 'sentry/api';
  2. import type {Config} from 'sentry/types/system';
  3. import {extractSlug} from 'sentry/utils/extractSlug';
  4. const BOOTSTRAP_URL = '/api/client-config/';
  5. const bootApplication = (data: Config) => {
  6. window.csrfCookieName = data.csrfCookieName;
  7. window.superUserCookieName = data.superUserCookieName;
  8. window.superUserCookieDomain = data.superUserCookieDomain ?? undefined;
  9. return data;
  10. };
  11. /**
  12. * Load the client configuration data using the BOOTSTRAP_URL. Used when
  13. * running in standalone SPA mode.
  14. */
  15. async function bootWithHydration() {
  16. const response = await fetch(BOOTSTRAP_URL);
  17. const data: Config = await response.json();
  18. // Shim up the initialData payload to quack like it came from
  19. // a customer-domains initial request. Because our initial call to BOOTSTRAP_URL
  20. // will not be on a customer domain, the response will not include this context.
  21. if (data.customerDomain === null && window.__SENTRY_DEV_UI) {
  22. const domain = extractSlug(window.location.host);
  23. if (domain) {
  24. data.customerDomain = {
  25. organizationUrl: `https://${domain.slug}.sentry.io`,
  26. sentryUrl: 'https://sentry.io',
  27. subdomain: domain.slug,
  28. };
  29. }
  30. }
  31. window.__initialData = data;
  32. bootApplication(data);
  33. preloadOrganizationData(data);
  34. return data;
  35. }
  36. async function promiseRequest(url: string) {
  37. try {
  38. const response = await fetch(url, {
  39. method: 'GET',
  40. headers: {
  41. Accept: 'application/json; charset=utf-8',
  42. 'Content-Type': 'application/json',
  43. 'sentry-trace': window.__initialData.initialTrace.sentry_trace,
  44. baggage: window.__initialData.initialTrace.baggage,
  45. },
  46. credentials: 'include',
  47. priority: 'high',
  48. });
  49. if (response.status >= 200 && response.status < 300) {
  50. const text = await response.text();
  51. const json = JSON.parse(text);
  52. const responseMeta: ResponseMeta = {
  53. status: response.status,
  54. statusText: response.statusText,
  55. responseJSON: json,
  56. responseText: text,
  57. getResponseHeader: (header: string) => response.headers.get(header),
  58. };
  59. return [json, response.statusText, responseMeta];
  60. }
  61. // eslint-disable-next-line no-throw-literal
  62. throw [response.status, response.statusText];
  63. } catch (error) {
  64. // eslint-disable-next-line no-throw-literal
  65. throw [error.status, error.statusText];
  66. }
  67. }
  68. function preloadOrganizationData(config: Config) {
  69. if (!config.shouldPreloadData) {
  70. // Don't send requests if we're not supposed to preload data.
  71. // See https://github.com/getsentry/sentry/blob/760afb3ab9d2bed669df2f2a01e58c438ceafa3c/src/sentry/web/client_config.py#L394-L418
  72. return;
  73. }
  74. let slug = config.lastOrganization;
  75. if (!slug && config.customerDomain) {
  76. slug = config.customerDomain.subdomain;
  77. }
  78. let host = '';
  79. if (config.links?.regionUrl && config.links?.regionUrl !== config.links?.sentryUrl) {
  80. host = config.links.regionUrl;
  81. }
  82. // When running in 'dev-ui' mode we need to use /region/$region instead of
  83. // subdomains so that webpack/vercel can proxy requests.
  84. if (host && window.__SENTRY_DEV_UI) {
  85. const domainpattern = /https?\:\/\/([^.]*)\.sentry.io/;
  86. const domainmatch = host.match(domainpattern);
  87. if (domainmatch) {
  88. host = `/region/${domainmatch[1]}`;
  89. }
  90. }
  91. function makeUrl(suffix: string) {
  92. return host + '/api/0/organizations/' + slug + suffix;
  93. }
  94. const preloadPromises: Record<string, any> = {orgSlug: slug};
  95. window.__sentry_preload = preloadPromises;
  96. try {
  97. if (!slug) {
  98. return;
  99. }
  100. preloadPromises.organization = promiseRequest(
  101. makeUrl('/?detailed=0&include_feature_flags=1')
  102. );
  103. preloadPromises.projects = promiseRequest(
  104. makeUrl('/projects/?all_projects=1&collapse=latestDeploys&collapse=unusedFeatures')
  105. );
  106. preloadPromises.teams = promiseRequest(makeUrl('/teams/'));
  107. } catch (e) {
  108. // eslint-disable-next-line
  109. console.error(e);
  110. }
  111. }
  112. /**
  113. * Load client configuration bootstrap data. This will detect if the app is
  114. * running in SPA mode or being booted from the django-rendered layout.html
  115. * template.
  116. */
  117. export async function bootstrap() {
  118. const bootstrapData = window.__initialData;
  119. // If __initialData is not already set on the window, we are likely running in
  120. // pure SPA mode, meaning django is not serving our frontend application and we
  121. // need to make an API request to hydrate the bootstrap data to boot the app.
  122. if (bootstrapData === undefined) {
  123. return await bootWithHydration();
  124. }
  125. return bootApplication(bootstrapData);
  126. }