From 7f56b0f7a320c270b3d8d24b706743339fb6e33c Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 8 Apr 2022 12:35:26 -0400 Subject: [PATCH] ref(integrations): Remove old `angular`, `ember`, and `vue` integrations (#4893) --- packages/integrations/src/angular.ts | 129 --------- packages/integrations/src/ember.ts | 73 ----- packages/integrations/src/index.ts | 3 - packages/integrations/src/vue.ts | 417 --------------------------- 4 files changed, 622 deletions(-) delete mode 100644 packages/integrations/src/angular.ts delete mode 100644 packages/integrations/src/ember.ts delete mode 100644 packages/integrations/src/vue.ts diff --git a/packages/integrations/src/angular.ts b/packages/integrations/src/angular.ts deleted file mode 100644 index dc71042700ff..000000000000 --- a/packages/integrations/src/angular.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; -import { getGlobalObject, logger } from '@sentry/utils'; - -import { IS_DEBUG_BUILD } from './flags'; - -// See https://github.com/angular/angular.js/blob/v1.4.7/src/minErr.js -const angularPattern = /^\[((?:[$a-zA-Z0-9]+:)?(?:[$a-zA-Z0-9]+))\] (.*?)\n?(\S+)$/; - -/** - * AngularJS integration - * - * Provides an $exceptionHandler for AngularJS - */ -export class Angular implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'AngularJS'; - - /** - * moduleName used in Angular's DI resolution algorithm - */ - public static moduleName: string = 'ngSentry'; - - /** - * @inheritDoc - */ - public name: string = Angular.id; - - /** - * Angular's instance - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly _angular: any; - - /** - * ngSentry module instance - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly _module: any; - - /** - * Returns current hub. - */ - private _getCurrentHub?: () => Hub; - - /** - * @inheritDoc - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(options: { angular?: any } = {}) { - IS_DEBUG_BUILD && logger.log('You are still using the Angular integration, consider moving to @sentry/angular'); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - this._angular = options.angular || getGlobalObject().angular; - - if (!this._angular) { - IS_DEBUG_BUILD && logger.error('AngularIntegration is missing an Angular instance'); - return; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this._module = this._angular.module(Angular.moduleName, []); - } - - /** - * @inheritDoc - */ - public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (!this._module) { - return; - } - - this._getCurrentHub = getCurrentHub; - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this._module.config([ - '$provide', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ($provide: any): void => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - $provide.decorator('$exceptionHandler', ['$delegate', this._$exceptionHandlerDecorator.bind(this)]); - }, - ]); - } - - /** - * Angular's exceptionHandler for Sentry integration - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _$exceptionHandlerDecorator($delegate: any): any { - return (exception: Error, cause?: string): void => { - const hub = this._getCurrentHub && this._getCurrentHub(); - - if (hub && hub.getIntegration(Angular)) { - hub.withScope(scope => { - if (cause) { - scope.setExtra('cause', cause); - } - - scope.addEventProcessor((event: Event) => { - const ex = event.exception && event.exception.values && event.exception.values[0]; - - if (ex) { - const matches = angularPattern.exec(ex.value || ''); - - if (matches) { - // This type now becomes something like: $rootScope:inprog - ex.type = matches[1]; - ex.value = matches[2]; - event.message = `${ex.type}: ${ex.value}`; - // auto set a new tag specifically for the angular error url - event.extra = { - ...event.extra, - angularDocs: matches[3].substr(0, 250), - }; - } - } - - return event; - }); - - hub.captureException(exception); - }); - } - $delegate(exception, cause); - }; - } -} diff --git a/packages/integrations/src/ember.ts b/packages/integrations/src/ember.ts deleted file mode 100644 index 81bbd1a0036b..000000000000 --- a/packages/integrations/src/ember.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { EventProcessor, Hub, Integration } from '@sentry/types'; -import { getGlobalObject, isInstanceOf, logger } from '@sentry/utils'; - -import { IS_DEBUG_BUILD } from './flags'; - -/** JSDoc */ -export class Ember implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Ember'; - - /** - * @inheritDoc - */ - public name: string = Ember.id; - - /** - * @inheritDoc - */ - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any - private readonly _Ember: any; - - /** - * @inheritDoc - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(options: { Ember?: any } = {}) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - this._Ember = options.Ember || getGlobalObject().Ember; - } - - /** - * @inheritDoc - */ - public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (!this._Ember) { - IS_DEBUG_BUILD && logger.error('EmberIntegration is missing an Ember instance'); - return; - } - - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ - const oldOnError = this._Ember.onerror; - - this._Ember.onerror = (error: Error): void => { - if (getCurrentHub().getIntegration(Ember)) { - getCurrentHub().captureException(error, { originalException: error }); - } - - if (typeof oldOnError === 'function') { - oldOnError.call(this._Ember, error); - } else if (this._Ember.testing) { - throw error; - } - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this._Ember.RSVP.on('error', (reason: unknown): void => { - if (getCurrentHub().getIntegration(Ember)) { - getCurrentHub().withScope(scope => { - if (isInstanceOf(reason, Error)) { - scope.setExtra('context', 'Unhandled Promise error detected'); - getCurrentHub().captureException(reason, { originalException: reason as Error }); - } else { - scope.setExtra('reason', reason); - getCurrentHub().captureMessage('Unhandled Promise error detected'); - } - }); - } - }); - } - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ -} diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index f1ba52e92026..9a2573ee5a44 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -1,12 +1,9 @@ -export { Angular } from './angular'; export { CaptureConsole } from './captureconsole'; export { Debug } from './debug'; export { Dedupe } from './dedupe'; -export { Ember } from './ember'; export { ExtraErrorData } from './extraerrordata'; export { Offline } from './offline'; export { ReportingObserver } from './reportingobserver'; export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; export { Transaction } from './transaction'; -export { Vue } from './vue'; diff --git a/packages/integrations/src/vue.ts b/packages/integrations/src/vue.ts deleted file mode 100644 index 12c55f53df32..000000000000 --- a/packages/integrations/src/vue.ts +++ /dev/null @@ -1,417 +0,0 @@ -/* eslint-disable max-lines */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { EventProcessor, Hub, Integration, IntegrationClass, Scope, Span, Transaction } from '@sentry/types'; -import { basename, getGlobalObject, logger, timestampWithMs } from '@sentry/utils'; - -import { IS_DEBUG_BUILD } from './flags'; - -/** - * Used to extract BrowserTracing integration from @sentry/tracing - */ -const BROWSER_TRACING_GETTER = { - id: 'BrowserTracing', -} as any as IntegrationClass; - -const VUE_OP = 'ui.vue'; - -/** Global Vue object limited to the methods/attributes we require */ -interface VueInstance { - config: { - errorHandler?(error: Error, vm?: ViewModel, info?: string): void; - }; - util?: { - warn(...input: any): void; - }; - mixin(hooks: { [key: string]: () => void }): void; -} - -/** Representation of Vue component internals */ -interface ViewModel { - [key: string]: any; - // eslint-disable-next-line @typescript-eslint/ban-types - $root: object; - $options: { - [key: string]: any; - name?: string; - propsData?: { [key: string]: any }; - _componentTag?: string; - __file?: string; - $_sentryPerfHook?: boolean; - }; - $once(hook: string, cb: () => void): void; -} - -/** Vue Integration configuration */ -interface IntegrationOptions { - /** Vue instance to be used inside the integration */ - Vue: VueInstance; - - /** - * When set to `false`, Sentry will suppress reporting of all props data - * from your Vue components for privacy concerns. - */ - attachProps: boolean; - /** - * When set to `true`, original Vue's `logError` will be called as well. - * https://github.com/vuejs/vue/blob/c2b1cfe9ccd08835f2d99f6ce60f67b4de55187f/src/core/util/error.js#L38-L48 - */ - logErrors: boolean; - - /** - * When set to `true`, enables tracking of components lifecycle performance. - * It requires `Tracing` integration to be also enabled. - */ - tracing: boolean; - - /** {@link TracingOptions} */ - tracingOptions: TracingOptions; -} - -/** Vue specific configuration for Tracing Integration */ -interface TracingOptions { - /** - * Decides whether to track components by hooking into its lifecycle methods. - * Can be either set to `boolean` to enable/disable tracking for all of them. - * Or to an array of specific component names (case-sensitive). - */ - trackComponents: boolean | string[]; - /** How long to wait until the tracked root activity is marked as finished and sent of to Sentry */ - timeout: number; - /** - * List of hooks to keep track of during component lifecycle. - * Available hooks: 'activate' | 'create' | 'destroy' | 'mount' | 'update' - * Based on https://vuejs.org/v2/api/#Options-Lifecycle-Hooks - */ - hooks: Operation[]; -} - -/** Optional metadata attached to Sentry Event */ -interface Metadata { - [key: string]: any; - componentName?: string; - propsData?: { [key: string]: any }; - lifecycleHook?: string; -} - -// https://vuejs.org/v2/api/#Options-Lifecycle-Hooks -type Hook = - | 'activated' - | 'beforeCreate' - | 'beforeDestroy' - | 'beforeMount' - | 'beforeUpdate' - | 'created' - | 'deactivated' - | 'destroyed' - | 'mounted' - | 'updated'; - -type Operation = 'activate' | 'create' | 'destroy' | 'mount' | 'update'; - -// Mappings from operation to corresponding lifecycle hook. -const HOOKS: { [key in Operation]: Hook[] } = { - activate: ['activated', 'deactivated'], - create: ['beforeCreate', 'created'], - destroy: ['beforeDestroy', 'destroyed'], - mount: ['beforeMount', 'mounted'], - update: ['beforeUpdate', 'updated'], -}; - -const COMPONENT_NAME_REGEXP = /(?:^|[-_/])(\w)/g; -const ROOT_COMPONENT_NAME = 'root'; -const ANONYMOUS_COMPONENT_NAME = 'anonymous component'; - -/** JSDoc */ -export class Vue implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Vue'; - - /** - * @inheritDoc - */ - public name: string = Vue.id; - - private readonly _options: IntegrationOptions; - - /** - * Cache holding already processed component names - */ - private readonly _componentsCache: { [key: string]: string } = {}; - private _rootSpan?: Span; - private _rootSpanTimer?: ReturnType; - - /** - * @inheritDoc - */ - public constructor( - options: Partial & { tracingOptions: Partial }>, - ) { - IS_DEBUG_BUILD && logger.log('You are still using the Vue.js integration, consider moving to @sentry/vue'); - this._options = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - Vue: getGlobalObject().Vue, - attachProps: true, - logErrors: false, - tracing: false, - ...options, - tracingOptions: { - hooks: ['mount', 'update'], - timeout: 2000, - trackComponents: false, - ...options.tracingOptions, - }, - }; - } - - /** - * @inheritDoc - */ - public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (!this._options.Vue) { - IS_DEBUG_BUILD && logger.error('Vue integration is missing a Vue instance'); - return; - } - - this._attachErrorHandler(getCurrentHub); - - if (this._options.tracing) { - this._startTracing(getCurrentHub); - } - } - - /** - * Extract component name from the ViewModel - */ - private _getComponentName(vm: ViewModel): string { - // Such level of granularity is most likely not necessary, but better safe than sorry. — Kamil - if (!vm) { - return ANONYMOUS_COMPONENT_NAME; - } - - if (vm.$root === vm) { - return ROOT_COMPONENT_NAME; - } - - if (!vm.$options) { - return ANONYMOUS_COMPONENT_NAME; - } - - if (vm.$options.name) { - return vm.$options.name; - } - - if (vm.$options._componentTag) { - return vm.$options._componentTag; - } - - // injected by vue-loader - if (vm.$options.__file) { - const unifiedFile = vm.$options.__file.replace(/^[a-zA-Z]:/, '').replace(/\\/g, '/'); - const filename = basename(unifiedFile, '.vue'); - return ( - this._componentsCache[filename] || - (this._componentsCache[filename] = filename.replace(COMPONENT_NAME_REGEXP, (_, c: string) => - c ? c.toUpperCase() : '', - )) - ); - } - - return ANONYMOUS_COMPONENT_NAME; - } - - /** Keep it as attribute function, to keep correct `this` binding inside the hooks callbacks */ - // eslint-disable-next-line @typescript-eslint/typedef - private readonly _applyTracingHooks = (vm: ViewModel, getCurrentHub: () => Hub): void => { - // Don't attach twice, just in case - if (vm.$options.$_sentryPerfHook) { - return; - } - vm.$options.$_sentryPerfHook = true; - - const name = this._getComponentName(vm); - const rootMount = name === ROOT_COMPONENT_NAME; - const spans: { [key: string]: Span } = {}; - - // Render hook starts after once event is emitted, - // but it ends before the second event of the same type. - // - // Because of this, we start measuring inside the first event, - // but finish it before it triggers, to skip the event emitter timing itself. - const rootHandler = (hook: Hook): void => { - const now = timestampWithMs(); - - // On the first handler call (before), it'll be undefined, as `$once` will add it in the future. - // However, on the second call (after), it'll be already in place. - if (this._rootSpan) { - this._finishRootSpan(now, getCurrentHub); - } else { - vm.$once(`hook:${hook}`, () => { - // Create an activity on the first event call. There'll be no second call, as rootSpan will be in place, - // thus new event handler won't be attached. - const activeTransaction = getActiveTransaction(getCurrentHub()); - if (activeTransaction) { - this._rootSpan = activeTransaction.startChild({ - description: 'Application Render', - op: VUE_OP, - }); - } - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ - }); - } - }; - - const childHandler = (hook: Hook, operation: Operation): void => { - // Skip components that we don't want to track to minimize the noise and give a more granular control to the user - const shouldTrack = Array.isArray(this._options.tracingOptions.trackComponents) - ? this._options.tracingOptions.trackComponents.indexOf(name) > -1 - : this._options.tracingOptions.trackComponents; - - if (!this._rootSpan || !shouldTrack) { - return; - } - - const now = timestampWithMs(); - const span = spans[operation]; - - // On the first handler call (before), it'll be undefined, as `$once` will add it in the future. - // However, on the second call (after), it'll be already in place. - if (span) { - span.finish(); - this._finishRootSpan(now, getCurrentHub); - } else { - vm.$once(`hook:${hook}`, () => { - if (this._rootSpan) { - spans[operation] = this._rootSpan.startChild({ - description: `Vue <${name}>`, - op: `${VUE_OP}.${operation}`, - }); - } - }); - } - }; - - // Each component has it's own scope, so all activities are only related to one of them - this._options.tracingOptions.hooks.forEach(operation => { - // Retrieve corresponding hooks from Vue lifecycle. - // eg. mount => ['beforeMount', 'mounted'] - const internalHooks = HOOKS[operation]; - - if (!internalHooks) { - IS_DEBUG_BUILD && logger.warn(`Unknown hook: ${operation}`); - return; - } - - internalHooks.forEach(internalHook => { - const handler = rootMount - ? rootHandler.bind(this, internalHook) - : childHandler.bind(this, internalHook, operation); - const currentValue = vm.$options[internalHook]; - - if (Array.isArray(currentValue)) { - vm.$options[internalHook] = [handler, ...currentValue]; - } else if (typeof currentValue === 'function') { - vm.$options[internalHook] = [handler, currentValue]; - } else { - vm.$options[internalHook] = [handler]; - } - }); - }); - }; - - /** Finish top-level span and activity with a debounce configured using `timeout` option */ - private _finishRootSpan(timestamp: number, _getCurrentHub: () => Hub): void { - if (this._rootSpanTimer) { - clearTimeout(this._rootSpanTimer); - } - - this._rootSpanTimer = setTimeout(() => { - // We should always finish the span - if (this._rootSpan) { - this._rootSpan.finish(timestamp); - } - }, this._options.tracingOptions.timeout); - } - - /** Inject configured tracing hooks into Vue's component lifecycles */ - private _startTracing(getCurrentHub: () => Hub): void { - const applyTracingHooks = this._applyTracingHooks; - - this._options.Vue.mixin({ - beforeCreate(this: ViewModel): void { - if (getCurrentHub().getIntegration(BROWSER_TRACING_GETTER)) { - // `this` points to currently rendered component - applyTracingHooks(this, getCurrentHub); - } else { - IS_DEBUG_BUILD && - logger.error('Vue integration has tracing enabled, but Tracing integration is not configured'); - } - }, - }); - } - - /** Inject Sentry's handler into owns Vue's error handler */ - private _attachErrorHandler(getCurrentHub: () => Hub): void { - // eslint-disable-next-line @typescript-eslint/unbound-method - const currentErrorHandler = this._options.Vue.config.errorHandler; - - this._options.Vue.config.errorHandler = (error: Error, vm?: ViewModel, info?: string): void => { - const metadata: Metadata = {}; - - if (vm) { - try { - metadata.componentName = this._getComponentName(vm); - - if (this._options.attachProps) { - metadata.propsData = vm.$options.propsData; - } - } catch (_oO) { - IS_DEBUG_BUILD && logger.warn('Unable to extract metadata from Vue component.'); - } - } - - if (info) { - metadata.lifecycleHook = info; - } - - if (getCurrentHub().getIntegration(Vue)) { - // Capture exception in the next event loop, to make sure that all breadcrumbs are recorded in time. - setTimeout(() => { - getCurrentHub().withScope(scope => { - scope.setContext('vue', metadata); - getCurrentHub().captureException(error); - }); - }); - } - - if (typeof currentErrorHandler === 'function') { - currentErrorHandler.call(this._options.Vue, error, vm, info); - } - - if (this._options.logErrors) { - if (this._options.Vue.util) { - this._options.Vue.util.warn(`Error in ${info}: "${error && error.toString()}"`, vm); - } - // eslint-disable-next-line no-console - console.error(error); - } - }; - } -} - -interface HubType extends Hub { - getScope?(): Scope | undefined; -} - -/** Grabs active transaction off scope */ -export function getActiveTransaction(hub: HubType): T | undefined { - if (hub && hub.getScope) { - const scope = hub.getScope() as Scope; - if (scope) { - return scope.getTransaction() as T | undefined; - } - } - - return undefined; -}