From 3373e068efbeb502e96168217f937598168ac6ff Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 13 Oct 2023 13:46:09 +0200 Subject: [PATCH 1/5] fix(loader): Improve loader callback handling --- .../templates/sentry/js-sdk-loader.js.tmpl | 64 ++++++++++--------- .../sentry/js-sdk-loader.min.js.tmpl | 2 +- src/sentry/templates/sentry/js-sdk-loader.ts | 62 +++++++++--------- 3 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl index 79d8bcfda7b05e..7d79c2f446c38d 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl @@ -27,7 +27,7 @@ // We only want to lazy inject/load the sdk bundle if // an error or promise rejection occured // OR someone called `capture...` on the SDK - injectSdk(onLoadCallbacks); + injectSdk(); } queue.data.push(content); }; @@ -47,7 +47,7 @@ : e, }); } - function injectSdk(callbacks) { + function injectSdk() { if (injected) { return; } @@ -64,29 +64,31 @@ _newScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded _newScriptTag.addEventListener('load', function () { - try { - _window.removeEventListener(_errorEvent, onError); - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); - // Add loader as SDK source - _window.SENTRY_SDK_SOURCE = 'loader'; - var SDK_1 = _window[_namespace]; - var oldInit_1 = SDK_1.init; - // Configure it using provided DSN and config object - SDK_1.init = function (options) { - var target = _config; - for (var key in options) { - if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; + setTimeout(function () { + try { + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); + // Add loader as SDK source + _window.SENTRY_SDK_SOURCE = 'loader'; + var SDK_1 = _window[_namespace]; + var oldInit_1 = SDK_1.init; + // Configure it using provided DSN and config object + SDK_1.init = function (options) { + var target = _config; + for (var key in options) { + if (Object.prototype.hasOwnProperty.call(options, key)) { + target[key] = options[key]; + } } - } - setupDefaultIntegrations(target, SDK_1); - oldInit_1(target); - }; - sdkLoaded(callbacks, SDK_1); - } - catch (o_O) { - console.error(o_O); - } + setupDefaultIntegrations(target, SDK_1); + oldInit_1(target); + }; + sdkLoaded(SDK_1); + } + catch (o_O) { + console.error(o_O); + } + }); }); _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); } @@ -115,12 +117,12 @@ __sentry.hub && __sentry.hub.getClient()); } - function sdkLoaded(callbacks, SDK) { + function sdkLoaded(SDK) { try { // We have to make sure to call all callbacks first - for (var i = 0; i < callbacks.length; i++) { - if (typeof callbacks[i] === 'function') { - callbacks[i](); + for (var i = 0; i < onLoadCallbacks.length; i++) { + if (typeof onLoadCallbacks[i] === 'function') { + onLoadCallbacks[i](); } } var data = queue.data; @@ -171,13 +173,13 @@ if (lazy && !forceLoad) { return; } - injectSdk(onLoadCallbacks); + injectSdk(); }; _window[_namespace].forceLoad = function () { forceLoad = true; if (lazy) { setTimeout(function () { - injectSdk(onLoadCallbacks); + injectSdk(); }); } }; @@ -199,7 +201,7 @@ _window.addEventListener(_unhandledrejectionEvent, onUnhandledRejection); if (!lazy) { setTimeout(function () { - injectSdk(onLoadCallbacks); + injectSdk(); }); } })(window, document, 'error', 'unhandledrejection', 'Sentry', '{{ publicKey|safe }}', '{{ jsSdkUrl|safe }}', {{ config|to_json|safe }}, {{ isLazy|safe|lower }}); 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 6e2d19bc06880d..edb748b0ad64e8 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl @@ -1 +1 @@ -{% load sentry_helpers %}!function(e,n,r,t,i,o,a,c,s){for(var f=s,forceLoad=!1,u=0;u-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&h(d),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(o){if(!p){p=!0;var s=n.scripts[0],f=n.createElement("script");f.src=a,f.crossOrigin="anonymous",f.addEventListener("load",(function(){try{e.removeEventListener(r,_),e.removeEventListener(t,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],a=n.init;n.init=function(e){var r=c;for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(r[t]=e[t]);!function(e,n){var r=e.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(e){return e.name}));e.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&r.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&r.push(new n.Replay);e.integrations=r}(r,n),a(r)},function(n,r){try{for(var t=0;t-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&h(),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(){if(!p){p=!0;var o=n.scripts[0],s=n.createElement("script");s.src=a,s.crossOrigin="anonymous",s.addEventListener("load",(function(){setTimeout((function(){try{e.removeEventListener(t,_),e.removeEventListener(r,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],o=n.init;n.init=function(e){var t=c;for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);!function(e,n){var t=e.integrations||[];if(!Array.isArray(t))return;var r=t.map((function(e){return e.name}));e.tracesSampleRate&&-1===r.indexOf("BrowserTracing")&&t.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===r.indexOf("Replay")&&t.push(new n.Replay);e.integrations=t}(t,n),o(t)},function(n){try{for(var t=0;t { + try { + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); - // Add loader as SDK source - _window.SENTRY_SDK_SOURCE = 'loader'; + // Add loader as SDK source + _window.SENTRY_SDK_SOURCE = 'loader'; - const SDK = _window[_namespace]; + const SDK = _window[_namespace]; - const oldInit = SDK.init; + const oldInit = SDK.init; - // Configure it using provided DSN and config object - SDK.init = function (options) { - const target = _config; - for (const key in options) { - if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; + // Configure it using provided DSN and config object + SDK.init = function (options) { + const target = _config; + for (const key in options) { + if (Object.prototype.hasOwnProperty.call(options, key)) { + target[key] = options[key]; + } } - } - setupDefaultIntegrations(target, SDK); - oldInit(target); - }; + setupDefaultIntegrations(target, SDK); + oldInit(target); + }; - sdkLoaded(callbacks, SDK); - } catch (o_O) { - console.error(o_O); - } + sdkLoaded(SDK); + } catch (o_O) { + console.error(o_O); + } + }); }); _currentScriptTag.parentNode!.insertBefore(_newScriptTag, _currentScriptTag); @@ -159,12 +161,12 @@ declare const __LOADER__IS_LAZY__: any; ); } - function sdkLoaded(callbacks, SDK) { + function sdkLoaded(SDK) { try { // We have to make sure to call all callbacks first - for (let i = 0; i < callbacks.length; i++) { - if (typeof callbacks[i] === 'function') { - callbacks[i](); + for (let i = 0; i < onLoadCallbacks.length; i++) { + if (typeof onLoadCallbacks[i] === 'function') { + onLoadCallbacks[i](); } } @@ -221,14 +223,14 @@ declare const __LOADER__IS_LAZY__: any; if (lazy && !forceLoad) { return; } - injectSdk(onLoadCallbacks); + injectSdk(); }; _window[_namespace].forceLoad = function () { forceLoad = true; if (lazy) { setTimeout(function () { - injectSdk(onLoadCallbacks); + injectSdk(); }); } }; @@ -253,7 +255,7 @@ declare const __LOADER__IS_LAZY__: any; if (!lazy) { setTimeout(function () { - injectSdk(onLoadCallbacks); + injectSdk(); }); } })( From 17e5f7b06b6c6be0282cdc479354abfe85158a29 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 17 Oct 2023 13:27:23 +0200 Subject: [PATCH 2/5] ref: adjust logic --- .../templates/sentry/js-sdk-loader.js.tmpl | 71 ++++++++-------- .../sentry/js-sdk-loader.min.js.tmpl | 2 +- src/sentry/templates/sentry/js-sdk-loader.ts | 83 +++++++++---------- 3 files changed, 73 insertions(+), 83 deletions(-) diff --git a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl index 7d79c2f446c38d..e01bb65aec4d45 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl @@ -1,6 +1,5 @@ {% load sentry_helpers %}(function sentryLoader(_window, _document, _errorEvent, _unhandledrejectionEvent, _namespace, _publicKey, _sdkBundleUrl, _config, _lazy) { var lazy = _lazy; - var forceLoad = false; for (var i = 0; i < document.scripts.length; i++) { if (document.scripts[i].src.indexOf(_publicKey) > -1) { // If lazy was set to true above, we need to check if the user has set data-lazy="no" @@ -47,6 +46,32 @@ : e, }); } + function onSentryScriptLoaded() { + try { + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); + // Add loader as SDK source + _window.SENTRY_SDK_SOURCE = 'loader'; + var SDK_1 = _window[_namespace]; + var oldInit_1 = SDK_1.init; + // Configure it using provided DSN and config object + SDK_1.init = function (options) { + var target = _config; + for (var key in options) { + if (Object.prototype.hasOwnProperty.call(options, key)) { + target[key] = options[key]; + } + } + setupDefaultIntegrations(target, SDK_1); + oldInit_1(target); + }; + // Wait a tick to ensure that all `Sentry.onLoad()` callbacks have been registered + setTimeout(function () { return setupSDK(SDK_1); }); + } + catch (o_O) { + console.error(o_O); + } + } function injectSdk() { if (injected) { return; @@ -63,32 +88,9 @@ _newScriptTag.src = _sdkBundleUrl; _newScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded - _newScriptTag.addEventListener('load', function () { - setTimeout(function () { - try { - _window.removeEventListener(_errorEvent, onError); - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); - // Add loader as SDK source - _window.SENTRY_SDK_SOURCE = 'loader'; - var SDK_1 = _window[_namespace]; - var oldInit_1 = SDK_1.init; - // Configure it using provided DSN and config object - SDK_1.init = function (options) { - var target = _config; - for (var key in options) { - if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; - } - } - setupDefaultIntegrations(target, SDK_1); - oldInit_1(target); - }; - sdkLoaded(SDK_1); - } - catch (o_O) { - console.error(o_O); - } - }); + _newScriptTag.addEventListener('load', onSentryScriptLoaded, { + once: true, + passive: true, }); _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); } @@ -117,7 +119,7 @@ __sentry.hub && __sentry.hub.getClient()); } - function sdkLoaded(SDK) { + function setupSDK(SDK) { try { // We have to make sure to call all callbacks first for (var i = 0; i < onLoadCallbacks.length; i++) { @@ -170,18 +172,11 @@ _window[_namespace] = _window[_namespace] || {}; _window[_namespace].onLoad = function (callback) { onLoadCallbacks.push(callback); - if (lazy && !forceLoad) { - return; - } - injectSdk(); }; _window[_namespace].forceLoad = function () { - forceLoad = true; - if (lazy) { - setTimeout(function () { - injectSdk(); - }); - } + setTimeout(function () { + injectSdk(); + }); }; [ 'init', 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 edb748b0ad64e8..420b634dbf4db4 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl @@ -1 +1 @@ -{% load sentry_helpers %}!function(e,n,t,r,i,o,a,c,s){for(var f=s,forceLoad=!1,u=0;u-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&h(),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(){if(!p){p=!0;var o=n.scripts[0],s=n.createElement("script");s.src=a,s.crossOrigin="anonymous",s.addEventListener("load",(function(){setTimeout((function(){try{e.removeEventListener(t,_),e.removeEventListener(r,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],o=n.init;n.init=function(e){var t=c;for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);!function(e,n){var t=e.integrations||[];if(!Array.isArray(t))return;var r=t.map((function(e){return e.name}));e.tracesSampleRate&&-1===r.indexOf("BrowserTracing")&&t.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===r.indexOf("Replay")&&t.push(new n.Replay);e.integrations=t}(t,n),o(t)},function(n){try{for(var t=0;t-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&E(),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(){try{e.removeEventListener(t,_),e.removeEventListener(r,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],o=n.init;n.init=function(e){var t=c;for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);!function(e,n){var t=e.integrations||[];if(!Array.isArray(t))return;var r=t.map((function(e){return e.name}));e.tracesSampleRate&&-1===r.indexOf("BrowserTracing")&&t.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===r.indexOf("Replay")&&t.push(new n.Replay);e.integrations=t}(t,n),o(t)},setTimeout((function(){return function(n){try{for(var t=0;t -1) { @@ -71,6 +70,38 @@ declare const __LOADER__IS_LAZY__: any; }); } + function onSentryScriptLoaded() { + try { + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); + + // Add loader as SDK source + _window.SENTRY_SDK_SOURCE = 'loader'; + + const SDK = _window[_namespace]; + + const oldInit = SDK.init; + + // Configure it using provided DSN and config object + SDK.init = function (options) { + const target = _config; + for (const key in options) { + if (Object.prototype.hasOwnProperty.call(options, key)) { + target[key] = options[key]; + } + } + + setupDefaultIntegrations(target, SDK); + oldInit(target); + }; + + // Wait a tick to ensure that all `Sentry.onLoad()` callbacks have been registered + setTimeout(() => setupSDK(SDK)); + } catch (o_O) { + console.error(o_O); + } + } + function injectSdk() { if (injected) { return; @@ -89,39 +120,10 @@ declare const __LOADER__IS_LAZY__: any; _newScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded - _newScriptTag.addEventListener('load', function () { - setTimeout(() => { - try { - _window.removeEventListener(_errorEvent, onError); - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); - - // Add loader as SDK source - _window.SENTRY_SDK_SOURCE = 'loader'; - - const SDK = _window[_namespace]; - - const oldInit = SDK.init; - - // Configure it using provided DSN and config object - SDK.init = function (options) { - const target = _config; - for (const key in options) { - if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; - } - } - - setupDefaultIntegrations(target, SDK); - oldInit(target); - }; - - sdkLoaded(SDK); - } catch (o_O) { - console.error(o_O); - } - }); + _newScriptTag.addEventListener('load', onSentryScriptLoaded, { + once: true, + passive: true, }); - _currentScriptTag.parentNode!.insertBefore(_newScriptTag, _currentScriptTag); } @@ -161,7 +163,7 @@ declare const __LOADER__IS_LAZY__: any; ); } - function sdkLoaded(SDK) { + function setupSDK(SDK) { try { // We have to make sure to call all callbacks first for (let i = 0; i < onLoadCallbacks.length; i++) { @@ -220,19 +222,12 @@ declare const __LOADER__IS_LAZY__: any; _window[_namespace].onLoad = function (callback) { onLoadCallbacks.push(callback); - if (lazy && !forceLoad) { - return; - } - injectSdk(); }; _window[_namespace].forceLoad = function () { - forceLoad = true; - if (lazy) { - setTimeout(function () { - injectSdk(); - }); - } + setTimeout(function () { + injectSdk(); + }); }; [ From 5dc7f4ffa29738b01650ea28a5676af6c934ccec Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 18 Oct 2023 13:14:23 +0200 Subject: [PATCH 3/5] Clean up naming a bunch --- src/sentry/templates/sentry/js-sdk-loader.ts | 72 +++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/sentry/templates/sentry/js-sdk-loader.ts b/src/sentry/templates/sentry/js-sdk-loader.ts index 3f35f4dd9059eb..06eb45e55a9912 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.ts +++ b/src/sentry/templates/sentry/js-sdk-loader.ts @@ -11,7 +11,7 @@ declare const __LOADER__IS_LAZY__: any; _namespace, _publicKey, _sdkBundleUrl, - _config, + _loaderInitConfig, _lazy ) { let lazy = _lazy; @@ -27,7 +27,6 @@ declare const __LOADER__IS_LAZY__: any; } } - let injected = false; const onLoadCallbacks: (() => void)[] = []; // Create a namespace and attach function that will store captured exception @@ -46,7 +45,7 @@ declare const __LOADER__IS_LAZY__: any; // We only want to lazy inject/load the sdk bundle if // an error or promise rejection occured // OR someone called `capture...` on the SDK - injectSdk(); + injectCDNScriptTag(); } queue.data.push(content); }; @@ -70,8 +69,9 @@ declare const __LOADER__IS_LAZY__: any; }); } - function onSentryScriptLoaded() { + function onSentryCDNScriptLoaded() { try { + // Remove the lazy mode error event listeners that we previously registered _window.removeEventListener(_errorEvent, onError); _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); @@ -80,19 +80,19 @@ declare const __LOADER__IS_LAZY__: any; const SDK = _window[_namespace]; - const oldInit = SDK.init; + const cdnInit = SDK.init; // Configure it using provided DSN and config object SDK.init = function (options) { - const target = _config; + const mergedInitOptions = _loaderInitConfig; for (const key in options) { if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; + mergedInitOptions[key] = options[key]; } } - setupDefaultIntegrations(target, SDK); - oldInit(target); + setupDefaultIntegrations(mergedInitOptions, SDK); + cdnInit(mergedInitOptions); }; // Wait a tick to ensure that all `Sentry.onLoad()` callbacks have been registered @@ -102,11 +102,16 @@ declare const __LOADER__IS_LAZY__: any; } } - function injectSdk() { - if (injected) { + let injectedCDNScriptTag = false; + + /** + * Injects script tag into the page pointing to the CDN bundle. + */ + function injectCDNScriptTag() { + if (injectedCDNScriptTag) { return; } - injected = true; + injectedCDNScriptTag = true; // Create a `script` tag with provided SDK `url` and attach it just before the first, already existing `script` tag // Scripts that are dynamically created and added to the document are async by default, @@ -114,17 +119,17 @@ declare const __LOADER__IS_LAZY__: any; // come out in the wrong order. Because of that we don't need async=1 as GA does. // it was probably(?) a legacy behavior that they left to not modify few years old snippet // https://www.html5rocks.com/en/tutorials/speed/script-loading/ - const _currentScriptTag = _document.scripts[0]; - const _newScriptTag = _document.createElement('script') as HTMLScriptElement; - _newScriptTag.src = _sdkBundleUrl; - _newScriptTag.crossOrigin = 'anonymous'; + const firstScriptTagInDom = _document.scripts[0]; + const cdnScriptTag = _document.createElement('script') as HTMLScriptElement; + cdnScriptTag.src = _sdkBundleUrl; + cdnScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded - _newScriptTag.addEventListener('load', onSentryScriptLoaded, { + cdnScriptTag.addEventListener('load', onSentryCDNScriptLoaded, { once: true, passive: true, }); - _currentScriptTag.parentNode!.insertBefore(_newScriptTag, _currentScriptTag); + firstScriptTagInDom.parentNode!.insertBefore(cdnScriptTag, firstScriptTagInDom); } // We want to ensure to only add default integrations if they haven't been added by the user. @@ -172,20 +177,22 @@ declare const __LOADER__IS_LAZY__: any; } } - const data = queue.data; + const buffer = queue.data; let initAlreadyCalled = sdkIsLoaded(); // Call init first, if provided - data.sort(a => (a.f === 'init' ? -1 : 0)); + buffer.sort(bufferItem => (bufferItem.f === 'init' ? -1 : 0)); + + // TODO: Refactor this to first call all inits or fall back to the normal init. // 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 let calledSentry = false; - for (let i = 0; i < data.length; i++) { - if (data[i].f) { + for (let i = 0; i < buffer.length; i++) { + if (buffer[i].f) { calledSentry = true; - const call = data[i]; + const call = buffer[i]; if (initAlreadyCalled === false && call.f !== 'init') { // First call always has to be init, this is a conveniece for the user so call to init is optional SDK.init(); @@ -199,17 +206,18 @@ declare const __LOADER__IS_LAZY__: any; SDK.init(); } + // TODO: change this comment. What is tracekit? // Because we installed the SDK, at this point we have an access to TraceKit's handler, // which can take care of browser differences (eg. missing exception argument in onerror) - const tracekitErrorHandler = _window.onerror; - const tracekitUnhandledRejectionHandler = _window.onunhandledrejection; + const sentryPatchedErrorHandler = _window.onerror; + const sentryPatchedUnhandledRejectionHandler = _window.onunhandledrejection; // And now capture all previously caught exceptions - for (let i = 0; i < data.length; i++) { - if ('e' in data[i] && tracekitErrorHandler) { - tracekitErrorHandler.apply(_window, data[i].e); - } else if ('p' in data[i] && tracekitUnhandledRejectionHandler) { - tracekitUnhandledRejectionHandler.apply(_window, [data[i].p]); + for (let i = 0; i < buffer.length; i++) { + if ('e' in buffer[i] && sentryPatchedErrorHandler) { + sentryPatchedErrorHandler.apply(_window, buffer[i].e); + } else if ('p' in buffer[i] && sentryPatchedUnhandledRejectionHandler) { + sentryPatchedUnhandledRejectionHandler.apply(_window, [buffer[i].p]); } } } catch (o_O) { @@ -226,7 +234,7 @@ declare const __LOADER__IS_LAZY__: any; _window[_namespace].forceLoad = function () { setTimeout(function () { - injectSdk(); + injectCDNScriptTag(); }); }; @@ -250,7 +258,7 @@ declare const __LOADER__IS_LAZY__: any; if (!lazy) { setTimeout(function () { - injectSdk(); + injectCDNScriptTag(); }); } })( From ca09bc61ff5fec8b1b343e375368ec9350d3b77a Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 18 Oct 2023 16:03:23 +0200 Subject: [PATCH 4/5] restructure a bit --- .../templates/sentry/js-sdk-loader.js.tmpl | 142 ++++++++++-------- .../sentry/js-sdk-loader.min.js.tmpl | 2 +- src/sentry/templates/sentry/js-sdk-loader.ts | 121 +++++++++------ 3 files changed, 150 insertions(+), 115 deletions(-) diff --git a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl index e01bb65aec4d45..8719c7e58250d9 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl @@ -1,4 +1,4 @@ -{% load sentry_helpers %}(function sentryLoader(_window, _document, _errorEvent, _unhandledrejectionEvent, _namespace, _publicKey, _sdkBundleUrl, _config, _lazy) { +{% load sentry_helpers %}(function sentryLoader(_window, _document, _errorEvent, _unhandledrejectionEvent, _namespace, _publicKey, _sdkBundleUrl, _loaderInitConfig, _lazy) { var lazy = _lazy; for (var i = 0; i < document.scripts.length; i++) { if (document.scripts[i].src.indexOf(_publicKey) > -1) { @@ -10,35 +10,40 @@ break; } } - var injected = false; var onLoadCallbacks = []; + function queueIsError(item) { + return 'e' in item; + } + function queueIsPromiseRejection(item) { + return 'p' in item; + } + function queueIsFunction(item) { + return 'f' in item; + } + var queue = []; // Create a namespace and attach function that will store captured exception // Because functions are also objects, we can attach the queue itself straight to it and save some bytes - var queue = function (content) { - // content.e = error - // content.p = promise rejection - // content.f = function call the Sentry - if (('e' in content || - 'p' in content || - (content.f && content.f.indexOf('capture') > -1) || - (content.f && content.f.indexOf('showReportDialog') > -1)) && - lazy) { + var enqueue = function (item) { + if (lazy && + (queueIsError(item) || + queueIsPromiseRejection(item) || + (queueIsFunction(item) && item.f.indexOf('capture') > -1) || + (queueIsFunction(item) && item.f.indexOf('showReportDialog') > -1))) { // We only want to lazy inject/load the sdk bundle if // an error or promise rejection occured // OR someone called `capture...` on the SDK - injectSdk(); + injectCDNScriptTag(); } - queue.data.push(content); + queue.push(item); }; - queue.data = []; function onError() { // Use keys as "data type" to save some characters" - queue({ + enqueue({ e: [].slice.call(arguments), }); } function onUnhandledRejection(e) { - queue({ + enqueue({ p: 'reason' in e ? e.reason : 'detail' in e && 'reason' in e.detail @@ -46,24 +51,26 @@ : e, }); } - function onSentryScriptLoaded() { + function onSentryCDNScriptLoaded() { try { - _window.removeEventListener(_errorEvent, onError); - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); // Add loader as SDK source _window.SENTRY_SDK_SOURCE = 'loader'; var SDK_1 = _window[_namespace]; - var oldInit_1 = SDK_1.init; + var cdnInit_1 = SDK_1.init; // Configure it using provided DSN and config object SDK_1.init = function (options) { - var target = _config; + // Remove the lazy mode error event listeners that we previously registered + // Once we call init, we can assume that Sentry has added it's own global error listeners + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); + var mergedInitOptions = _loaderInitConfig; for (var key in options) { if (Object.prototype.hasOwnProperty.call(options, key)) { - target[key] = options[key]; + mergedInitOptions[key] = options[key]; } } - setupDefaultIntegrations(target, SDK_1); - oldInit_1(target); + setupDefaultIntegrations(mergedInitOptions, SDK_1); + cdnInit_1(mergedInitOptions); }; // Wait a tick to ensure that all `Sentry.onLoad()` callbacks have been registered setTimeout(function () { return setupSDK(SDK_1); }); @@ -72,27 +79,31 @@ console.error(o_O); } } - function injectSdk() { - if (injected) { + var injectedCDNScriptTag = false; + /** + * Injects script tag into the page pointing to the CDN bundle. + */ + function injectCDNScriptTag() { + if (injectedCDNScriptTag) { return; } - injected = true; + injectedCDNScriptTag = true; // Create a `script` tag with provided SDK `url` and attach it just before the first, already existing `script` tag // Scripts that are dynamically created and added to the document are async by default, // they don't block rendering and execute as soon as they download, meaning they could // come out in the wrong order. Because of that we don't need async=1 as GA does. // it was probably(?) a legacy behavior that they left to not modify few years old snippet // https://www.html5rocks.com/en/tutorials/speed/script-loading/ - var _currentScriptTag = _document.scripts[0]; - var _newScriptTag = _document.createElement('script'); - _newScriptTag.src = _sdkBundleUrl; - _newScriptTag.crossOrigin = 'anonymous'; + var firstScriptTagInDom = _document.scripts[0]; + var cdnScriptTag = _document.createElement('script'); + cdnScriptTag.src = _sdkBundleUrl; + cdnScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded - _newScriptTag.addEventListener('load', onSentryScriptLoaded, { + cdnScriptTag.addEventListener('load', onSentryCDNScriptLoaded, { once: true, passive: true, }); - _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); + firstScriptTagInDom.parentNode.insertBefore(cdnScriptTag, firstScriptTagInDom); } // We want to ensure to only add default integrations if they haven't been added by the user. function setupDefaultIntegrations(config, SDK) { @@ -127,40 +138,38 @@ onLoadCallbacks[i](); } } - var data = queue.data; - var initAlreadyCalled = sdkIsLoaded(); - // Call init first, if provided - data.sort(function (a) { return (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 - var calledSentry = false; - for (var i = 0; i < data.length; i++) { - if (data[i].f) { - calledSentry = true; - var call = data[i]; - if (initAlreadyCalled === false && call.f !== 'init') { - // First call always has to be init, this is a conveniece for the user so call to init is optional - SDK.init(); - } - initAlreadyCalled = true; - SDK[call.f].apply(SDK, call.a); + // First call all inits from the queue + for (var i = 0; i < queue.length; i++) { + var item = queue[i]; + if (queueIsFunction(item) && item.f === 'init') { + SDK.init.apply(SDK, item.a); } } - if (initAlreadyCalled === false && calledSentry === false) { - // Sentry has never been called but we need Sentry.init() so call it + // If the SDK has not been called manually, either in an onLoad callback, or somewhere else, + // we initialize it for the user + if (!sdkIsLoaded()) { SDK.init(); } - // Because we installed the SDK, at this point we have an access to TraceKit's handler, + // Now, we _know_ that the SDK is initialized, and can continue with the rest of the queue + // Because we installed the SDK, at this point we can assume that the global handlers have been patched // which can take care of browser differences (eg. missing exception argument in onerror) - var tracekitErrorHandler = _window.onerror; - var tracekitUnhandledRejectionHandler = _window.onunhandledrejection; - // And now capture all previously caught exceptions - for (var i = 0; i < data.length; i++) { - if ('e' in data[i] && tracekitErrorHandler) { - tracekitErrorHandler.apply(_window, data[i].e); + var sentryPatchedErrorHandler = _window.onerror; + var sentryPatchedUnhandledRejectionHandler = _window.onunhandledrejection; + for (var i = 0; i < queue.length; i++) { + var item = queue[i]; + if (queueIsFunction(item)) { + // We already called all init before, so just skip this + if (item.f === 'init') { + continue; + } + SDK[item.f].apply(SDK, item.a); + } + else if (queueIsError(item) && sentryPatchedErrorHandler) { + sentryPatchedErrorHandler.apply(_window, item.e); } - else if ('p' in data[i] && tracekitUnhandledRejectionHandler) { - tracekitUnhandledRejectionHandler.apply(_window, [data[i].p]); + else if (queueIsPromiseRejection(item) && + sentryPatchedUnhandledRejectionHandler) { + sentryPatchedUnhandledRejectionHandler.apply(_window, [item.p]); } } } @@ -171,11 +180,16 @@ // We make sure we do not overwrite window.Sentry since there could be already integrations in there _window[_namespace] = _window[_namespace] || {}; _window[_namespace].onLoad = function (callback) { + // If the SDK was already loaded, call the callback immediately + if (sdkIsLoaded()) { + callback(); + return; + } onLoadCallbacks.push(callback); }; _window[_namespace].forceLoad = function () { setTimeout(function () { - injectSdk(); + injectCDNScriptTag(); }); }; [ @@ -189,14 +203,14 @@ 'showReportDialog', ].forEach(function (f) { _window[_namespace][f] = function () { - queue({ f: f, a: arguments }); + enqueue({ f: f, a: arguments }); }; }); _window.addEventListener(_errorEvent, onError); _window.addEventListener(_unhandledrejectionEvent, onUnhandledRejection); if (!lazy) { setTimeout(function () { - injectSdk(); + injectCDNScriptTag(); }); } })(window, document, 'error', 'unhandledrejection', 'Sentry', '{{ publicKey|safe }}', '{{ jsSdkUrl|safe }}', {{ config|to_json|safe }}, {{ isLazy|safe|lower }}); 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 420b634dbf4db4..5da7e58fa743a8 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl @@ -1 +1 @@ -{% load sentry_helpers %}!function(e,n,t,r,i,o,a,c,s){for(var f=s,u=0;u-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&E(),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(){try{e.removeEventListener(t,_),e.removeEventListener(r,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],o=n.init;n.init=function(e){var t=c;for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);!function(e,n){var t=e.integrations||[];if(!Array.isArray(t))return;var r=t.map((function(e){return e.name}));e.tracesSampleRate&&-1===r.indexOf("BrowserTracing")&&t.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===r.indexOf("Replay")&&t.push(new n.Replay);e.integrations=t}(t,n),o(t)},setTimeout((function(){return function(n){try{for(var t=0;t-1){s&&"no"===document.scripts[f].getAttribute("data-lazy")&&(s=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[],h=function(n){s&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&O(),v.push(n)};function y(){h({e:[].slice.call(arguments)})}function E(n){h({p:"reason"in n?n.reason:"detail"in n&&"reason"in n.detail?n.detail.reason:n})}function m(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,y),n.removeEventListener(t,E);var a=c;for(var u in i)Object.prototype.hasOwnProperty.call(i,u)&&(a[u]=i[u]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&r.push(new e.BrowserTracing);(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&r.push(new e.Replay);n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{for(var r=0;r void)[] = []; + // A captured error + type ErrorQueueItem = {e: any}; + // A captured promise rejection + type PromiseRejectionQueueItem = {p: any}; + // A captured function call to Sentry + type FunctionQueueItem = {a: IArguments; f: string}; + type QueueItem = ErrorQueueItem | PromiseRejectionQueueItem | FunctionQueueItem; + + function queueIsError(item: QueueItem): item is ErrorQueueItem { + return 'e' in item; + } + + function queueIsPromiseRejection(item: QueueItem): item is PromiseRejectionQueueItem { + return 'p' in item; + } + + function queueIsFunction(item: QueueItem): item is FunctionQueueItem { + return 'f' in item; + } + + const queue: QueueItem[] = []; + // Create a namespace and attach function that will store captured exception // Because functions are also objects, we can attach the queue itself straight to it and save some bytes - const queue = function (content) { - // content.e = error - // content.p = promise rejection - // content.f = function call the Sentry + const enqueue = function (item: QueueItem) { if ( - ('e' in content || - 'p' in content || - (content.f && content.f.indexOf('capture') > -1) || - (content.f && content.f.indexOf('showReportDialog') > -1)) && - lazy + lazy && + (queueIsError(item) || + queueIsPromiseRejection(item) || + (queueIsFunction(item) && item.f.indexOf('capture') > -1) || + (queueIsFunction(item) && item.f.indexOf('showReportDialog') > -1)) ) { // We only want to lazy inject/load the sdk bundle if // an error or promise rejection occured // OR someone called `capture...` on the SDK injectCDNScriptTag(); } - queue.data.push(content); + queue.push(item); }; - queue.data = []; function onError() { // Use keys as "data type" to save some characters" - queue({ + enqueue({ e: [].slice.call(arguments), }); } function onUnhandledRejection(e) { - queue({ + enqueue({ p: 'reason' in e ? e.reason @@ -71,10 +89,6 @@ declare const __LOADER__IS_LAZY__: any; function onSentryCDNScriptLoaded() { try { - // Remove the lazy mode error event listeners that we previously registered - _window.removeEventListener(_errorEvent, onError); - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); - // Add loader as SDK source _window.SENTRY_SDK_SOURCE = 'loader'; @@ -84,6 +98,11 @@ declare const __LOADER__IS_LAZY__: any; // Configure it using provided DSN and config object SDK.init = function (options) { + // Remove the lazy mode error event listeners that we previously registered + // Once we call init, we can assume that Sentry has added it's own global error listeners + _window.removeEventListener(_errorEvent, onError); + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); + const mergedInitOptions = _loaderInitConfig; for (const key in options) { if (Object.prototype.hasOwnProperty.call(options, key)) { @@ -177,47 +196,44 @@ declare const __LOADER__IS_LAZY__: any; } } - const buffer = queue.data; - - let initAlreadyCalled = sdkIsLoaded(); - - // Call init first, if provided - buffer.sort(bufferItem => (bufferItem.f === 'init' ? -1 : 0)); - - // TODO: Refactor this to first call all inits or fall back to the normal init. - - // 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 - let calledSentry = false; - for (let i = 0; i < buffer.length; i++) { - if (buffer[i].f) { - calledSentry = true; - const call = buffer[i]; - if (initAlreadyCalled === false && call.f !== 'init') { - // First call always has to be init, this is a conveniece for the user so call to init is optional - SDK.init(); - } - initAlreadyCalled = true; - SDK[call.f].apply(SDK, call.a); + // First call all inits from the queue + for (let i = 0; i < queue.length; i++) { + const item = queue[i]; + if (queueIsFunction(item) && item.f === 'init') { + SDK.init.apply(SDK, item.a); } } - if (initAlreadyCalled === false && calledSentry === false) { - // Sentry has never been called but we need Sentry.init() so call it + + // If the SDK has not been called manually, either in an onLoad callback, or somewhere else, + // we initialize it for the user + if (!sdkIsLoaded()) { SDK.init(); } - // TODO: change this comment. What is tracekit? - // Because we installed the SDK, at this point we have an access to TraceKit's handler, + // Now, we _know_ that the SDK is initialized, and can continue with the rest of the queue + + // Because we installed the SDK, at this point we can assume that the global handlers have been patched // which can take care of browser differences (eg. missing exception argument in onerror) const sentryPatchedErrorHandler = _window.onerror; const sentryPatchedUnhandledRejectionHandler = _window.onunhandledrejection; - // And now capture all previously caught exceptions - for (let i = 0; i < buffer.length; i++) { - if ('e' in buffer[i] && sentryPatchedErrorHandler) { - sentryPatchedErrorHandler.apply(_window, buffer[i].e); - } else if ('p' in buffer[i] && sentryPatchedUnhandledRejectionHandler) { - sentryPatchedUnhandledRejectionHandler.apply(_window, [buffer[i].p]); + for (let i = 0; i < queue.length; i++) { + const item = queue[i]; + + if (queueIsFunction(item)) { + // We already called all init before, so just skip this + if (item.f === 'init') { + continue; + } + + SDK[item.f].apply(SDK, item.a); + } else if (queueIsError(item) && sentryPatchedErrorHandler) { + sentryPatchedErrorHandler.apply(_window, item.e); + } else if ( + queueIsPromiseRejection(item) && + sentryPatchedUnhandledRejectionHandler + ) { + sentryPatchedUnhandledRejectionHandler.apply(_window, [item.p]); } } } catch (o_O) { @@ -229,6 +245,11 @@ declare const __LOADER__IS_LAZY__: any; _window[_namespace] = _window[_namespace] || {}; _window[_namespace].onLoad = function (callback) { + // If the SDK was already loaded, call the callback immediately + if (sdkIsLoaded()) { + callback(); + return; + } onLoadCallbacks.push(callback); }; @@ -249,7 +270,7 @@ declare const __LOADER__IS_LAZY__: any; 'showReportDialog', ].forEach(function (f) { _window[_namespace][f] = function () { - queue({f, a: arguments}); + enqueue({f, a: arguments}); }; }); From 17a1f7d415a64e03e48190b4709761852e905025 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 19 Oct 2023 09:51:30 +0200 Subject: [PATCH 5/5] minor ref --- src/sentry/templates/sentry/js-sdk-loader.js.tmpl | 4 ++-- src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl | 2 +- src/sentry/templates/sentry/js-sdk-loader.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl index 8719c7e58250d9..97b2f09dbe7298 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.js.tmpl @@ -23,7 +23,7 @@ var queue = []; // Create a namespace and attach function that will store captured exception // Because functions are also objects, we can attach the queue itself straight to it and save some bytes - var enqueue = function (item) { + function enqueue(item) { if (lazy && (queueIsError(item) || queueIsPromiseRejection(item) || @@ -35,7 +35,7 @@ injectCDNScriptTag(); } queue.push(item); - }; + } function onError() { // Use keys as "data type" to save some characters" enqueue({ 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 5da7e58fa743a8..7f7315d52d698b 100644 --- a/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl +++ b/src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl @@ -1 +1 @@ -{% load sentry_helpers %}!function(n,e,r,t,i,o,a,c,u){for(var s=u,f=0;f-1){s&&"no"===document.scripts[f].getAttribute("data-lazy")&&(s=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[],h=function(n){s&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&O(),v.push(n)};function y(){h({e:[].slice.call(arguments)})}function E(n){h({p:"reason"in n?n.reason:"detail"in n&&"reason"in n.detail?n.detail.reason:n})}function m(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,y),n.removeEventListener(t,E);var a=c;for(var u in i)Object.prototype.hasOwnProperty.call(i,u)&&(a[u]=i[u]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&r.push(new e.BrowserTracing);(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&r.push(new e.Replay);n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{for(var r=0;r-1){s&&"no"===document.scripts[f].getAttribute("data-lazy")&&(s=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[];function h(n){s&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&O(),v.push(n)}function y(){h({e:[].slice.call(arguments)})}function E(n){h({p:"reason"in n?n.reason:"detail"in n&&"reason"in n.detail?n.detail.reason:n})}function m(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,y),n.removeEventListener(t,E);var a=c;for(var u in i)Object.prototype.hasOwnProperty.call(i,u)&&(a[u]=i[u]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&r.push(new e.BrowserTracing);(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&r.push(new e.Replay);n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{for(var r=0;r