diff --git a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl index aee842cba6b899..8e933c5387b44b 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl @@ -67,11 +67,15 @@ _newScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded - _newScriptTag.addEventListener('load', function() { + _newScriptTag.addEventListener('load', function () { try { - // Restore onerror/onunhandledrejection handlers - _window[_onerror] = _oldOnerror; - _window[_onunhandledrejection] = _oldOnunhandledrejection; + // Restore onerror/onunhandledrejection handlers - only if not mutated in the meanwhile + if (_window[_onerror] && _window[_onerror].__SENTRY_LOADER__) { + _window[_onerror] = _oldOnerror; + } + if (_window[_onunhandledrejection] && _window[_onunhandledrejection].__SENTRY_LOADER__) { + _window[_onunhandledrejection] = _oldOnunhandledrejection; + } // Add loader as SDK source _window.SENTRY_SDK_SOURCE = 'loader'; @@ -80,6 +84,19 @@ var oldInit = SDK.init; + var integrations = []; + if (_config.tracesSampleRate) { + integrations.push(new Sentry.BrowserTracing()); + } + + if (_config.replaysSessionSampleRate || _config.replaysOnErrorSampleRate) { + integrations.push(new Sentry.Replay()); + } + + if (integrations.length) { + _config.integrations = integrations; + } + // Configure it using provided DSN and config object SDK.init = function(options) { var target = _config; @@ -100,10 +117,14 @@ _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); } + function sdkIsLoaded() { + var __sentry = _window['__SENTRY__']; + // If there is a global __SENTRY__ that means that in any of the callbacks init() was already invoked + return !!(!(typeof __sentry === 'undefined') && __sentry.hub && __sentry.hub.getClient()); + } + function sdkLoaded(callbacks, SDK) { try { - var data = queue.data; - // We have to make sure to call all callbacks first for (var i = 0; i < callbacks.length; i++) { if (typeof callbacks[i] === 'function') { @@ -111,12 +132,12 @@ } } - var initAlreadyCalled = false; - var __sentry = _window['__SENTRY__']; - // If there is a global __SENTRY__ that means that in any of the callbacks init() was already invoked - if (!(typeof __sentry === 'undefined') && __sentry.hub && __sentry.hub.getClient()) { - initAlreadyCalled = true; - } + var data = queue.data; + + var initAlreadyCalled = sdkIsLoaded(); + + // Call init first, if provided + data.sort((a, b) => a.f === 'init' ? -1 : 0); // We want to replay all calls to Sentry and also make sure that `init` is called if it wasn't already // We replay all calls to `Sentry.*` now @@ -176,7 +197,6 @@ } }; - [ 'init', 'addBreadcrumb', @@ -195,7 +215,7 @@ // Store reference to the old `onerror` handler and override it with our own function // that will just push exceptions to the queue and call through old handler if we found one var _oldOnerror = _window[_onerror]; - _window[_onerror] = function(message, source, lineno, colno, exception) { + _window[_onerror] = function() { // Use keys as "data type" to save some characters" queue({ e: [].slice.call(arguments) @@ -203,6 +223,7 @@ if (_oldOnerror) _oldOnerror.apply(_window, arguments); }; + _window[_onerror].__SENTRY_LOADER__ = true; // Do the same store/queue/call operations for `onunhandledrejection` event var _oldOnunhandledrejection = _window[_onunhandledrejection]; @@ -212,6 +233,7 @@ }); if (_oldOnunhandledrejection) _oldOnunhandledrejection.apply(_window, arguments); }; + _window[_onunhandledrejection].__SENTRY_LOADER__ = true; if (!lazy) { setTimeout(function () { diff --git a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl index 39b0f855f22bea..d45f71e760f234 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl @@ -1,4 +1,4 @@ -{% load sentry_helpers %}(function(c,w,C,q,r,k,D,E,x,l){function t(a){if(!y){y=!0;var f=w.scripts[0],d=w.createElement(C);d.src=E;d.crossOrigin="anonymous";d.addEventListener("load",function(){try{c[q]=u;c[r]=v;c.SENTRY_SDK_SOURCE="loader";var b=c[k],g=b.init;b.init=function(e){for(var h in e)Object.prototype.hasOwnProperty.call(e,h)&&(x[h]=e[h]);g(x)};F(a,b)}catch(e){console.error(e)}});f.parentNode.insertBefore(d,f)}}function F(a,f){try{for(var d=m.data,b=0;b None: pass - def _get_bundle_kind_modifier(self, key: ProjectKey, sdk_version: str) -> Tuple[str, bool]: + def _get_bundle_kind_modifier( + self, key: ProjectKey, sdk_version: str + ) -> Tuple[str, bool, bool, bool, bool]: """Returns a string that is used to modify the bundle name""" is_v7_sdk = sdk_version >= Version("7.0.0") is_lazy = True bundle_kind_modifier = "" + has_replay = get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_REPLAY) + has_performance = get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_PERFORMANCE) + has_debug = get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_DEBUG) # The order in which these modifiers are added is important, as the # bundle name is built up from left to right. # https://docs.sentry.io/platforms/javascript/install/cdn/ - if get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_PERFORMANCE): + # We depend on fixes in the tracing bundle that are only available in v7 + if is_v7_sdk and has_performance: bundle_kind_modifier += ".tracing" is_lazy = False - has_replay = get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_REPLAY) - # If the project does not have a v7 sdk set, we cannot load the replay bundle. if is_v7_sdk and has_replay: bundle_kind_modifier += ".replay" @@ -69,10 +77,10 @@ def _get_bundle_kind_modifier(self, key: ProjectKey, sdk_version: str) -> Tuple[ if is_v7_sdk and not has_replay: bundle_kind_modifier += ".es5" - if get_dynamic_sdk_loader_option(key, DynamicSdkLoaderOption.HAS_DEBUG): + if has_debug: bundle_kind_modifier += ".debug" - return bundle_kind_modifier, is_lazy + return bundle_kind_modifier, is_lazy, has_performance, has_replay, has_debug def _get_context( self, key: Optional[ProjectKey] @@ -89,7 +97,13 @@ def _get_context( sdk_version = get_browser_sdk_version(key) - bundle_kind_modifier, is_lazy = self._get_bundle_kind_modifier(key, sdk_version) + ( + bundle_kind_modifier, + is_lazy, + has_performance, + has_replay, + has_debug, + ) = self._get_bundle_kind_modifier(key, sdk_version) js_sdk_loader_default_sdk_url_template_slot_count = ( settings.JS_SDK_LOADER_DEFAULT_SDK_URL.count("%s") @@ -108,9 +122,21 @@ def _get_context( except TypeError: sdk_url = "" # It fails if it cannot inject the version in the string + config: SdkConfig = {"dsn": key.dsn_public} + + if has_debug: + config["debug"] = True + + if has_performance: + config["tracesSampleRate"] = 1 + + if has_replay: + config["replaysSessionSampleRate"] = 0.1 + config["replaysOnErrorSampleRate"] = 1 + return ( { - "config": {"dsn": key.dsn_public}, + "config": config, "jsSdkUrl": sdk_url, "publicKey": key.public_key, "isLazy": is_lazy, diff --git a/tests/sentry/web/frontend/test_js_sdk_loader.py b/tests/sentry/web/frontend/test_js_sdk_loader.py index 39b3b66fafd161..bb012aa708568a 100644 --- a/tests/sentry/web/frontend/test_js_sdk_loader.py +++ b/tests/sentry/web/frontend/test_js_sdk_loader.py @@ -8,6 +8,7 @@ from sentry.loader.dynamic_sdk_options import DynamicSdkLoaderOption from sentry.testutils import TestCase +from sentry.utils import json class JavaScriptSdkLoaderTest(TestCase): @@ -113,7 +114,9 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse settings.JS_SDK_LOADER_DEFAULT_SDK_URL = "https://browser.sentry-cdn.com/%s/bundle%s.min.js" settings.JS_SDK_LOADER_SDK_VERSION = "7.32.0" - for data, expected in [ + dsn = self.projectkey.get_dsn(public=True) + + for data, expected_bundle, expected_options in [ ( { "dynamicSdkLoaderOptions": { @@ -121,6 +124,7 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.tracing.es5.min.js", + {"dsn": dsn, "tracesSampleRate": 1}, ), ( { @@ -129,6 +133,7 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.es5.debug.min.js", + {"dsn": dsn, "debug": True}, ), ( { @@ -137,6 +142,7 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.replay.min.js", + {"dsn": dsn, "replaysSessionSampleRate": 0.1, "replaysOnErrorSampleRate": 1}, ), ( { @@ -146,6 +152,12 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.tracing.replay.min.js", + { + "dsn": dsn, + "tracesSampleRate": 1, + "replaysSessionSampleRate": 0.1, + "replaysOnErrorSampleRate": 1, + }, ), ( { @@ -155,6 +167,12 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.replay.debug.min.js", + { + "dsn": dsn, + "replaysSessionSampleRate": 0.1, + "replaysOnErrorSampleRate": 1, + "debug": True, + }, ), ( { @@ -164,6 +182,7 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.tracing.es5.debug.min.js", + {"dsn": dsn, "tracesSampleRate": 1, "debug": True}, ), ( { @@ -174,6 +193,13 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse } }, b"/7.37.0/bundle.tracing.replay.debug.min.js", + { + "dsn": dsn, + "tracesSampleRate": 1, + "replaysSessionSampleRate": 0.1, + "replaysOnErrorSampleRate": 1, + "debug": True, + }, ), ]: self.projectkey.data = data @@ -181,7 +207,13 @@ def test_bundle_kind_modifiers(self, load_version_from_file, get_selected_browse resp = self.client.get(self.path) assert resp.status_code == 200 self.assertTemplateUsed(resp, "sentry/js-sdk-loader.js.tmpl") - assert expected in resp.content + assert expected_bundle in resp.content + + for key in expected_options: + # Convert to e.g. "option_name": 0.1 + single_option = {key: expected_options[key]} + assert bytes(json.dumps(single_option)[1:-1], "utf-8") in resp.content + self.projectkey.data = {} self.projectkey.save()