integration.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { arrayify, logger } from '@sentry/utils';
  2. import { DEBUG_BUILD } from './debug-build.js';
  3. import { addGlobalEventProcessor } from './eventProcessors.js';
  4. import { getClient } from './exports.js';
  5. import { getCurrentHub } from './hub.js';
  6. const installedIntegrations = [];
  7. /** Map of integrations assigned to a client */
  8. /**
  9. * Remove duplicates from the given array, preferring the last instance of any duplicate. Not guaranteed to
  10. * preseve the order of integrations in the array.
  11. *
  12. * @private
  13. */
  14. function filterDuplicates(integrations) {
  15. const integrationsByName = {};
  16. integrations.forEach(currentInstance => {
  17. const { name } = currentInstance;
  18. const existingInstance = integrationsByName[name];
  19. // We want integrations later in the array to overwrite earlier ones of the same type, except that we never want a
  20. // default instance to overwrite an existing user instance
  21. if (existingInstance && !existingInstance.isDefaultInstance && currentInstance.isDefaultInstance) {
  22. return;
  23. }
  24. integrationsByName[name] = currentInstance;
  25. });
  26. return Object.keys(integrationsByName).map(k => integrationsByName[k]);
  27. }
  28. /** Gets integrations to install */
  29. function getIntegrationsToSetup(options) {
  30. const defaultIntegrations = options.defaultIntegrations || [];
  31. const userIntegrations = options.integrations;
  32. // We flag default instances, so that later we can tell them apart from any user-created instances of the same class
  33. defaultIntegrations.forEach(integration => {
  34. integration.isDefaultInstance = true;
  35. });
  36. let integrations;
  37. if (Array.isArray(userIntegrations)) {
  38. integrations = [...defaultIntegrations, ...userIntegrations];
  39. } else if (typeof userIntegrations === 'function') {
  40. integrations = arrayify(userIntegrations(defaultIntegrations));
  41. } else {
  42. integrations = defaultIntegrations;
  43. }
  44. const finalIntegrations = filterDuplicates(integrations);
  45. // The `Debug` integration prints copies of the `event` and `hint` which will be passed to `beforeSend` or
  46. // `beforeSendTransaction`. It therefore has to run after all other integrations, so that the changes of all event
  47. // processors will be reflected in the printed values. For lack of a more elegant way to guarantee that, we therefore
  48. // locate it and, assuming it exists, pop it out of its current spot and shove it onto the end of the array.
  49. const debugIndex = findIndex(finalIntegrations, integration => integration.name === 'Debug');
  50. if (debugIndex !== -1) {
  51. const [debugInstance] = finalIntegrations.splice(debugIndex, 1);
  52. finalIntegrations.push(debugInstance);
  53. }
  54. return finalIntegrations;
  55. }
  56. /**
  57. * Given a list of integration instances this installs them all. When `withDefaults` is set to `true` then all default
  58. * integrations are added unless they were already provided before.
  59. * @param integrations array of integration instances
  60. * @param withDefault should enable default integrations
  61. */
  62. function setupIntegrations(client, integrations) {
  63. const integrationIndex = {};
  64. integrations.forEach(integration => {
  65. // guard against empty provided integrations
  66. if (integration) {
  67. setupIntegration(client, integration, integrationIndex);
  68. }
  69. });
  70. return integrationIndex;
  71. }
  72. /**
  73. * Execute the `afterAllSetup` hooks of the given integrations.
  74. */
  75. function afterSetupIntegrations(client, integrations) {
  76. for (const integration of integrations) {
  77. // guard against empty provided integrations
  78. if (integration && integration.afterAllSetup) {
  79. integration.afterAllSetup(client);
  80. }
  81. }
  82. }
  83. /** Setup a single integration. */
  84. function setupIntegration(client, integration, integrationIndex) {
  85. if (integrationIndex[integration.name]) {
  86. DEBUG_BUILD && logger.log(`Integration skipped because it was already installed: ${integration.name}`);
  87. return;
  88. }
  89. integrationIndex[integration.name] = integration;
  90. // `setupOnce` is only called the first time
  91. if (installedIntegrations.indexOf(integration.name) === -1) {
  92. // eslint-disable-next-line deprecation/deprecation
  93. integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
  94. installedIntegrations.push(integration.name);
  95. }
  96. // `setup` is run for each client
  97. if (integration.setup && typeof integration.setup === 'function') {
  98. integration.setup(client);
  99. }
  100. if (client.on && typeof integration.preprocessEvent === 'function') {
  101. const callback = integration.preprocessEvent.bind(integration) ;
  102. client.on('preprocessEvent', (event, hint) => callback(event, hint, client));
  103. }
  104. if (client.addEventProcessor && typeof integration.processEvent === 'function') {
  105. const callback = integration.processEvent.bind(integration) ;
  106. const processor = Object.assign((event, hint) => callback(event, hint, client), {
  107. id: integration.name,
  108. });
  109. client.addEventProcessor(processor);
  110. }
  111. DEBUG_BUILD && logger.log(`Integration installed: ${integration.name}`);
  112. }
  113. /** Add an integration to the current hub's client. */
  114. function addIntegration(integration) {
  115. const client = getClient();
  116. if (!client || !client.addIntegration) {
  117. DEBUG_BUILD && logger.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`);
  118. return;
  119. }
  120. client.addIntegration(integration);
  121. }
  122. // Polyfill for Array.findIndex(), which is not supported in ES5
  123. function findIndex(arr, callback) {
  124. for (let i = 0; i < arr.length; i++) {
  125. if (callback(arr[i]) === true) {
  126. return i;
  127. }
  128. }
  129. return -1;
  130. }
  131. /**
  132. * Convert a new integration function to the legacy class syntax.
  133. * In v8, we can remove this and instead export the integration functions directly.
  134. *
  135. * @deprecated This will be removed in v8!
  136. */
  137. function convertIntegrationFnToClass(
  138. name,
  139. fn,
  140. ) {
  141. return Object.assign(
  142. function ConvertedIntegration(...args) {
  143. return fn(...args);
  144. },
  145. { id: name },
  146. ) ;
  147. }
  148. /**
  149. * Define an integration function that can be used to create an integration instance.
  150. * Note that this by design hides the implementation details of the integration, as they are considered internal.
  151. */
  152. function defineIntegration(fn) {
  153. return fn;
  154. }
  155. export { addIntegration, afterSetupIntegrations, convertIntegrationFnToClass, defineIntegration, getIntegrationsToSetup, installedIntegrations, setupIntegration, setupIntegrations };
  156. //# sourceMappingURL=integration.js.map