processInitQueue.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import {createRoot} from 'react-dom/client';
  2. import throttle from 'lodash/throttle';
  3. import {exportedGlobals} from 'sentry/bootstrap/exportGlobals';
  4. import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider';
  5. import type {OnSentryInitConfiguration} from 'sentry/types/system';
  6. import {SentryInitRenderReactComponent} from 'sentry/types/system';
  7. import {renderDom} from './renderDom';
  8. import {renderOnDomReady} from './renderOnDomReady';
  9. const COMPONENT_MAP = {
  10. [SentryInitRenderReactComponent.INDICATORS]: () =>
  11. import(/* webpackChunkName: "Indicators" */ 'sentry/components/indicators'),
  12. [SentryInitRenderReactComponent.SYSTEM_ALERTS]: () =>
  13. import(/* webpackChunkName: "SystemAlerts" */ 'sentry/views/app/systemAlerts'),
  14. [SentryInitRenderReactComponent.SETUP_WIZARD]: () =>
  15. import(/* webpackChunkName: "SetupWizard" */ 'sentry/views/setupWizard'),
  16. [SentryInitRenderReactComponent.U2F_SIGN]: () =>
  17. import(/* webpackChunkName: "U2fSign" */ 'sentry/components/u2f/u2fsign'),
  18. [SentryInitRenderReactComponent.SU_STAFF_ACCESS_FORM]: () =>
  19. import(
  20. /* webpackChunkName: "SuperuserStaffAccessForm" */ 'sentry/components/superuserStaffAccessForm'
  21. ),
  22. };
  23. async function processItem(initConfig: OnSentryInitConfiguration) {
  24. /**
  25. * Allows our auth pages to dynamically attach a client side password
  26. * strength indicator The password strength component is very
  27. * heavyweight as it includes the zxcvbn, a relatively byte-heavy
  28. * password strength estimation library. Load it on demand.
  29. */
  30. if (initConfig.name === 'passwordStrength') {
  31. if (!initConfig.input || !initConfig.element) {
  32. return;
  33. }
  34. const inputElem = document.querySelector(initConfig.input);
  35. const rootEl = document.querySelector(initConfig.element);
  36. if (!inputElem || !rootEl) {
  37. return;
  38. }
  39. const {PasswordStrength} = await import(
  40. /* webpackChunkName: "PasswordStrength" */ 'sentry/components/passwordStrength'
  41. );
  42. const root = createRoot(rootEl);
  43. inputElem.addEventListener(
  44. 'input',
  45. throttle(e => {
  46. root.render(
  47. /**
  48. * The screens and components rendering here will always render in light mode.
  49. * This is because config is not available at this point (user might not be logged in yet),
  50. * and so we dont know which theme to pick.
  51. */
  52. <ThemeAndStyleProvider>
  53. <PasswordStrength value={e.target.value} />
  54. </ThemeAndStyleProvider>
  55. );
  56. })
  57. );
  58. return;
  59. }
  60. /**
  61. * Allows server rendered templates to render a React component to DOM
  62. * without exposing the component globally.
  63. */
  64. if (initConfig.name === 'renderReact') {
  65. if (!COMPONENT_MAP.hasOwnProperty(initConfig.component)) {
  66. return;
  67. }
  68. const {default: Component} = await COMPONENT_MAP[initConfig.component]();
  69. renderOnDomReady(() =>
  70. // TODO(ts): Unsure how to type this, complains about u2fsign's required props
  71. renderDom(
  72. (props: any) => (
  73. /**
  74. * The screens and components rendering here will always render in light mode.
  75. * This is because config is not available at this point (user might not be logged in yet),
  76. * and so we dont know which theme to pick.
  77. */
  78. <ThemeAndStyleProvider>
  79. <Component {...props} />
  80. </ThemeAndStyleProvider>
  81. ),
  82. initConfig.container,
  83. initConfig.props
  84. )
  85. );
  86. }
  87. /**
  88. * Callback for when js bundle is loaded. Provide library + component references
  89. * for downstream consumers to use.
  90. */
  91. if (initConfig.name === 'onReady' && typeof initConfig.onReady === 'function') {
  92. initConfig.onReady(exportedGlobals);
  93. }
  94. }
  95. /**
  96. * This allows server templates to push "tasks" to be run after application has initialized.
  97. * The global `window.__onSentryInit` is used for this.
  98. *
  99. * Be careful here as we can not guarantee type safety on `__onSentryInit` as
  100. * these will be defined in server rendered templates
  101. */
  102. export async function processInitQueue() {
  103. // Currently, this is run *before* anything is queued in
  104. // `window.__onSentryInit`. We want to provide a migration path for potential
  105. // custom plugins that rely on `window.SentryApp` so they can start migrating
  106. // their plugins ASAP, as `SentryApp` will be loaded async and will require
  107. // callbacks to access it, instead of via `window` global.
  108. if (
  109. typeof window.__onSentryInit !== 'undefined' &&
  110. !Array.isArray(window.__onSentryInit)
  111. ) {
  112. return;
  113. }
  114. const queued = window.__onSentryInit;
  115. // Stub future calls of `window.__onSentryInit.push` so that it is
  116. // processed immediately (since bundle is loaded at this point and no
  117. // longer needs to act as a queue)
  118. //
  119. window.__onSentryInit = {
  120. push: processItem,
  121. };
  122. if (Array.isArray(queued)) {
  123. // These are all side-effects, so no need to return a value, but allow consumer to
  124. // wait for all initialization to finish
  125. await Promise.all(queued.map(processItem));
  126. }
  127. }