import * as Sentry from '@sentry/react'; import isUndefined from 'lodash/isUndefined'; import Reflux from 'reflux'; import {HookName, Hooks} from 'app/types/hooks'; /** * See types/hooks for hook usage reference. */ const validHookNames = new Set([ '_', 'analytics:event', 'analytics:init-user', 'analytics:track-adhoc-event', 'analytics:track-event', 'analytics:log-experiment', 'component:disabled-member', 'component:disabled-member-tooltip', 'component:header-date-range', 'component:header-selector-items', 'component:global-notifications', 'component:member-list-header', 'component:dashboards-header', 'feature-disabled:alerts-page', 'feature-disabled:alert-wizard-performance', 'feature-disabled:configure-distributed-tracing', 'feature-disabled:custom-inbound-filters', 'feature-disabled:custom-symbol-sources', 'feature-disabled:data-forwarding', 'feature-disabled:discard-groups', 'feature-disabled:discover-page', 'feature-disabled:discover-saved-query-create', 'feature-disabled:discover-sidebar-item', 'feature-disabled:discover2-page', 'feature-disabled:discover2-sidebar-item', 'feature-disabled:events-page', 'feature-disabled:events-sidebar-item', 'feature-disabled:grid-editable-actions', 'feature-disabled:open-discover', 'feature-disabled:dashboards-edit', 'feature-disabled:dashboards-page', 'feature-disabled:dashboards-sidebar-item', 'feature-disabled:incidents-sidebar-item', 'feature-disabled:performance-new-project', 'feature-disabled:performance-page', 'feature-disabled:performance-quick-trace', 'feature-disabled:performance-sidebar-item', 'feature-disabled:project-performance-score-card', 'feature-disabled:project-selector-checkbox', 'feature-disabled:rate-limits', 'feature-disabled:sso-basic', 'feature-disabled:sso-rippling', 'feature-disabled:sso-saml2', 'feature-disabled:trace-view-link', 'footer', 'help-modal:footer', 'integrations:feature-gates', 'member-invite-modal:customization', 'metrics:event', 'onboarding:extra-chrome', 'onboarding-wizard:skip-help', 'organization:header', 'routes', 'routes:admin', 'routes:api', 'routes:organization', 'routes:organization-root', 'settings:api-navigation-config', 'settings:organization-navigation', 'settings:organization-navigation-config', 'sidebar:bottom-items', 'sidebar:help-menu', 'sidebar:item-label', 'sidebar:item-override', 'sidebar:organization-dropdown-menu', 'sidebar:organization-dropdown-menu-bottom', ]); type HookStoreInterface = { // XXX(epurkhiser): We could type this as {[H in HookName]?: // Array}, however this causes typescript to produce a complex // union that it complains is 'too complex' hooks: any; add(hookName: H, callback: Hooks[H]): void; remove(hookName: H, callback: Hooks[H]): void; get(hookName: H): Array; }; const hookStoreConfig: Reflux.StoreDefinition & HookStoreInterface = { hooks: {}, init() { this.hooks = {}; }, add(hookName, callback) { // Gracefully error on invalid hooks, but maintain registration // TODO(ts): With typescript we can remove this in the future if (!validHookNames.has(hookName)) { // eslint-disable-next-line no-console console.error('Invalid hook name: ' + hookName); Sentry.withScope(scope => { scope.setExtra('hookName', hookName); Sentry.captureException(new Error('Invalid hook name')); }); } if (isUndefined(this.hooks[hookName])) { this.hooks[hookName] = []; } this.hooks[hookName]!.push(callback); this.trigger(hookName, this.hooks[hookName]); }, remove(hookName, callback) { if (isUndefined(this.hooks[hookName])) { return; } this.hooks[hookName] = this.hooks[hookName]!.filter(cb => cb !== callback); this.trigger(hookName, this.hooks[hookName]); }, get(hookName) { return this.hooks[hookName]! || []; }, }; type HookStore = Reflux.Store & HookStoreInterface; /** * HookStore is used to allow extensibility into Sentry's frontend via * registration of 'hook functions'. * * This functionality is primarily used by the SASS sentry.io product. */ const HookStore = Reflux.createStore(hookStoreConfig) as HookStore; export default HookStore;