diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index 798a068b5adf..b430007b552d 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -401,14 +401,5 @@ function getObjectClassName(obj: unknown): string | undefined | void { /** If a plain object has a property that is an `Error`, return this error. */ function getErrorPropertyFromObject(obj: Record): Error | undefined { - for (const prop in obj) { - if (Object.prototype.hasOwnProperty.call(obj, prop)) { - const value = obj[prop]; - if (value instanceof Error) { - return value; - } - } - } - - return undefined; + return Object.values(obj).find((v): v is Error => v instanceof Error); } diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 93c87e1d6161..dbd753320065 100644 --- a/packages/browser/src/helpers.ts +++ b/packages/browser/src/helpers.ts @@ -104,8 +104,9 @@ export function wrap( } // Wrap the function itself - // It is important that `sentryWrapped` is not an arrow function to preserve the context of `this` - const sentryWrapped = function (this: unknown, ...args: unknown[]): unknown { + // It is important that `sW` is not an arrow function to preserve the context of `this` + // The name `sW` is matched by the frame stripping regex in stacktrace.ts + const sW = function (this: unknown, ...args: unknown[]): unknown { try { // Also wrap arguments that are themselves functions const wrappedArguments = args.map(arg => wrap(arg, options)); @@ -145,7 +146,7 @@ export function wrap( try { for (const property in fn) { if (Object.prototype.hasOwnProperty.call(fn, property)) { - sentryWrapped[property as keyof T] = fn[property as keyof T]; + sW[property as keyof T] = fn[property as keyof T]; } } } catch { @@ -155,16 +156,16 @@ export function wrap( // Signal that this function has been wrapped/filled already // for both debugging and to prevent it to being wrapped/filled twice - markFunctionWrapped(sentryWrapped, fn); + markFunctionWrapped(sW, fn); - addNonEnumerableProperty(fn, '__sentry_wrapped__', sentryWrapped); + addNonEnumerableProperty(fn, '__sentry_wrapped__', sW); // Restore original function name (not all browsers allow that) try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name')!; + const descriptor = Object.getOwnPropertyDescriptor(sW, 'name')!; if (descriptor.configurable) { - Object.defineProperty(sentryWrapped, 'name', { + Object.defineProperty(sW, 'name', { get(): string { return fn.name; }, @@ -175,7 +176,7 @@ export function wrap( // to save some bytes we simply try-catch this } - return sentryWrapped; + return sW; } /** diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 79022dc6e31e..de99621bf52f 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -287,13 +287,7 @@ function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFe return; } - const breadcrumbData: FetchBreadcrumbData = { - method: handlerData.fetchData.method, - url: handlerData.fetchData.url, - }; - if (handlerData.error) { - const data: FetchBreadcrumbData = handlerData.fetchData; const hint: FetchBreadcrumbHint = { data: handlerData.error, input: handlerData.args, @@ -303,7 +297,7 @@ function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFe const breadcrumb = { category: 'fetch', - data, + data: handlerData.fetchData, level: 'error', type: 'http', } satisfies Breadcrumb; @@ -318,10 +312,6 @@ function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFe status_code: response?.status, }; - breadcrumbData.request_body_size = handlerData.fetchData.request_body_size; - breadcrumbData.response_body_size = handlerData.fetchData.response_body_size; - breadcrumbData.status_code = response?.status; - const hint: FetchBreadcrumbHint = { input: handlerData.args, response, diff --git a/packages/browser/src/integrations/browserapierrors.ts b/packages/browser/src/integrations/browserapierrors.ts index 7e94c2bc7167..cd32435fa5b0 100644 --- a/packages/browser/src/integrations/browserapierrors.ts +++ b/packages/browser/src/integrations/browserapierrors.ts @@ -2,39 +2,11 @@ import type { IntegrationFn, WrappedFunction } from '@sentry/core'; import { defineIntegration, fill, getFunctionName, getOriginalFunction } from '@sentry/core'; import { WINDOW, wrap } from '../helpers'; -const DEFAULT_EVENT_TARGET = [ - 'EventTarget', - 'Window', - 'Node', - 'ApplicationCache', - 'AudioTrackList', - 'BroadcastChannel', - 'ChannelMergerNode', - 'CryptoOperation', - 'EventSource', - 'FileReader', - 'HTMLUnknownElement', - 'IDBDatabase', - 'IDBRequest', - 'IDBTransaction', - 'KeyOperation', - 'MediaController', - 'MessagePort', - 'ModalWindow', - 'Notification', - 'SVGElementInstance', - 'Screen', - 'SharedWorker', - 'TextTrack', - 'TextTrackCue', - 'TextTrackList', - 'WebSocket', - 'WebSocketWorker', - 'Worker', - 'XMLHttpRequest', - 'XMLHttpRequestEventTarget', - 'XMLHttpRequestUpload', -]; +// Using a comma-separated string and split for smaller bundle size vs an array literal +const DEFAULT_EVENT_TARGET = + 'EventTarget,Window,Node,ApplicationCache,AudioTrackList,BroadcastChannel,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,SharedWorker,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload'.split( + ',', + ); const INTEGRATION_NAME = 'BrowserApiErrors'; diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index c8cd806d0062..8438adccbee5 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -162,29 +162,19 @@ function _enhanceEventWithInitialFrame( line: number | undefined, column: number | undefined, ): Event { - // event.exception const e = (event.exception = event.exception || {}); - // event.exception.values const ev = (e.values = e.values || []); - // event.exception.values[0] const ev0 = (ev[0] = ev[0] || {}); - // event.exception.values[0].stacktrace const ev0s = (ev0.stacktrace = ev0.stacktrace || {}); - // event.exception.values[0].stacktrace.frames const ev0sf = (ev0s.frames = ev0s.frames || []); - const colno = column; - const lineno = line; - const filename = getFilenameFromUrl(url) ?? getLocationHref(); - - // event.exception.values[0].stacktrace.frames if (ev0sf.length === 0) { ev0sf.push({ - colno, - filename, + colno: column, + filename: getFilenameFromUrl(url) ?? getLocationHref(), function: UNKNOWN_FUNCTION, in_app: true, - lineno, + lineno: line, }); } diff --git a/packages/browser/src/utils/detectBrowserExtension.ts b/packages/browser/src/utils/detectBrowserExtension.ts index 52e667ccecf2..95ad7cebcf06 100644 --- a/packages/browser/src/utils/detectBrowserExtension.ts +++ b/packages/browser/src/utils/detectBrowserExtension.ts @@ -55,11 +55,11 @@ function _isEmbeddedBrowserExtension(): boolean { } const href = getLocationHref(); - const extensionProtocols = ['chrome-extension', 'moz-extension', 'ms-browser-extension', 'safari-web-extension']; // Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage const isDedicatedExtensionPage = - WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}://`)); + WINDOW === WINDOW.top && + /^(?:chrome-extension|moz-extension|ms-browser-extension|safari-web-extension):\/\//.test(href); return !isDedicatedExtensionPage; } diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index 8a6688fe5953..8c206413c96c 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -3,33 +3,51 @@ import { getClient, SDK_VERSION } from '@sentry/core'; import type { BrowserClient } from '../client'; import { WINDOW } from '../helpers'; -// This is a map of integration function method to bundle file name. -const LazyLoadableIntegrations = { - replayIntegration: 'replay', +// Integration names that can be lazy-loaded from the CDN. +// Bundle file names are derived: strip 'Integration' suffix, lowercase. +// Exceptions (hyphenated bundle names) are listed separately. +// Using comma-separated string + split for smaller bundle size. +const LAZY_LOADABLE_NAMES = + 'replayIntegration,replayCanvasIntegration,feedbackIntegration,feedbackModalIntegration,feedbackScreenshotIntegration,captureConsoleIntegration,contextLinesIntegration,linkedErrorsIntegration,dedupeIntegration,extraErrorDataIntegration,graphqlClientIntegration,httpClientIntegration,reportingObserverIntegration,rewriteFramesIntegration,browserProfilingIntegration,moduleMetadataIntegration,instrumentAnthropicAiClient,instrumentOpenAiClient,instrumentGoogleGenAIClient,instrumentLangGraph,createLangChainCallbackHandler'.split( + ',', + ); + +// Bundle names that don't follow the simple lowercase derivation pattern +const HYPHENATED_BUNDLES: Record = { replayCanvasIntegration: 'replay-canvas', - feedbackIntegration: 'feedback', feedbackModalIntegration: 'feedback-modal', feedbackScreenshotIntegration: 'feedback-screenshot', - captureConsoleIntegration: 'captureconsole', - contextLinesIntegration: 'contextlines', - linkedErrorsIntegration: 'linkederrors', - dedupeIntegration: 'dedupe', - extraErrorDataIntegration: 'extraerrordata', - graphqlClientIntegration: 'graphqlclient', - httpClientIntegration: 'httpclient', - reportingObserverIntegration: 'reportingobserver', - rewriteFramesIntegration: 'rewriteframes', - browserProfilingIntegration: 'browserprofiling', - moduleMetadataIntegration: 'modulemetadata', - instrumentAnthropicAiClient: 'instrumentanthropicaiclient', - instrumentOpenAiClient: 'instrumentopenaiclient', - instrumentGoogleGenAIClient: 'instrumentgooglegenaiclient', - instrumentLangGraph: 'instrumentlanggraph', - createLangChainCallbackHandler: 'createlangchaincallbackhandler', -} as const; +}; + +type LazyLoadableIntegrationName = + | 'replayIntegration' + | 'replayCanvasIntegration' + | 'feedbackIntegration' + | 'feedbackModalIntegration' + | 'feedbackScreenshotIntegration' + | 'captureConsoleIntegration' + | 'contextLinesIntegration' + | 'linkedErrorsIntegration' + | 'dedupeIntegration' + | 'extraErrorDataIntegration' + | 'graphqlClientIntegration' + | 'httpClientIntegration' + | 'reportingObserverIntegration' + | 'rewriteFramesIntegration' + | 'browserProfilingIntegration' + | 'moduleMetadataIntegration' + | 'instrumentAnthropicAiClient' + | 'instrumentOpenAiClient' + | 'instrumentGoogleGenAIClient' + | 'instrumentLangGraph' + | 'createLangChainCallbackHandler'; + +function getBundleName(name: string): string { + return HYPHENATED_BUNDLES[name] || name.replace('Integration', '').toLowerCase(); +} const WindowWithMaybeIntegration = WINDOW as { - Sentry?: Partial>; + Sentry?: Partial>; }; /** @@ -37,10 +55,10 @@ const WindowWithMaybeIntegration = WINDOW as { * Rejects if the integration cannot be loaded. */ export async function lazyLoadIntegration( - name: keyof typeof LazyLoadableIntegrations, + name: LazyLoadableIntegrationName, scriptNonce?: string, ): Promise { - const bundle = LazyLoadableIntegrations[name]; + const bundle = LAZY_LOADABLE_NAMES.includes(name) ? getBundleName(name) : undefined; // `window.Sentry` is only set when using a CDN bundle, but this method can also be used via the NPM package const sentryOnWindow = (WindowWithMaybeIntegration.Sentry = WindowWithMaybeIntegration.Sentry || {}); diff --git a/packages/core/src/utils/stacktrace.ts b/packages/core/src/utils/stacktrace.ts index 16a32ede4e58..c4664267cb2e 100644 --- a/packages/core/src/utils/stacktrace.ts +++ b/packages/core/src/utils/stacktrace.ts @@ -88,7 +88,7 @@ export function stripSentryFramesAndReverse(stack: ReadonlyArray): S const localStack = Array.from(stack); // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call) - if (/sentryWrapped/.test(getLastStackFrame(localStack).function || '')) { + if (/sentryWrapped|^sW$/.test(getLastStackFrame(localStack).function || '')) { localStack.pop(); }