Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 104 additions & 93 deletions src/sentry/templates/sentry/js-sdk-loader.js.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% 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;
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"
Expand All @@ -11,84 +10,100 @@
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) {
function enqueue(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(onLoadCallbacks);
injectCDNScriptTag();
}
queue.data.push(content);
};
queue.data = [];
queue.push(item);
}
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
? e.detail.reason
: e,
});
}
function injectSdk(callbacks) {
if (injected) {
function onSentryCDNScriptLoaded() {
try {
// Add loader as SDK source
_window.SENTRY_SDK_SOURCE = 'loader';
var SDK_1 = _window[_namespace];
var cdnInit_1 = SDK_1.init;
// Configure it using provided DSN and config object
SDK_1.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);
var mergedInitOptions = _loaderInitConfig;
for (var key in options) {
if (Object.prototype.hasOwnProperty.call(options, key)) {
mergedInitOptions[key] = options[key];
}
}
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); });
}
catch (o_O) {
console.error(o_O);
}
}
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', 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);
}
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) {
Expand All @@ -115,48 +130,46 @@
__sentry.hub &&
__sentry.hub.getClient());
}
function sdkLoaded(callbacks, SDK) {
function setupSDK(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;
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]);
}
}
}
Expand All @@ -167,19 +180,17 @@
// 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) {
onLoadCallbacks.push(callback);
if (lazy && !forceLoad) {
// If the SDK was already loaded, call the callback immediately
if (sdkIsLoaded()) {
callback();
return;
}
injectSdk(onLoadCallbacks);
onLoadCallbacks.push(callback);
};
_window[_namespace].forceLoad = function () {
forceLoad = true;
if (lazy) {
setTimeout(function () {
injectSdk(onLoadCallbacks);
});
}
setTimeout(function () {
injectCDNScriptTag();
});
};
[
'init',
Expand All @@ -192,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(onLoadCallbacks);
injectCDNScriptTag();
});
}
})(window, document, 'error', 'unhandledrejection', 'Sentry', '{{ publicKey|safe }}', '{{ jsSdkUrl|safe }}', {{ config|to_json|safe }}, {{ isLazy|safe|lower }});
2 changes: 1 addition & 1 deletion src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{% load sentry_helpers %}!function(e,n,r,t,i,o,a,c,s){for(var f=s,forceLoad=!1,u=0;u<document.scripts.length;u++)if(document.scripts[u].src.indexOf(o)>-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<n.length;t++)"function"==typeof n[t]&&n[t]();var i=l.data,o=!(void 0===(u=e.__SENTRY__)||!u.hub||!u.hub.getClient());i.sort((function(e){return"init"===e.f?-1:0}));var a=!1;for(t=0;t<i.length;t++)if(i[t].f){a=!0;var c=i[t];!1===o&&"init"!==c.f&&r.init(),o=!0,r[c.f].apply(r,c.a)}!1===o&&!1===a&&r.init();var s=e.onerror,f=e.onunhandledrejection;for(t=0;t<i.length;t++)"e"in i[t]&&s?s.apply(e,i[t].e):"p"in i[t]&&f&&f.apply(e,[i[t].p])}catch(e){console.error(e)}var u}(o,n)}catch(e){console.error(e)}})),s.parentNode.insertBefore(f,s)}}l.data=[],e[i]=e[i]||{},e[i].onLoad=function(e){d.push(e),f&&!forceLoad||h(d)},e[i].forceLoad=function(){forceLoad=!0,f&&setTimeout((function(){h(d)}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(n){e[i][n]=function(){l({f:n,a:arguments})}})),e.addEventListener(r,_),e.addEventListener(t,v),f||setTimeout((function(){h(d)}))}(window,document,"error","unhandledrejection","Sentry",'{{ publicKey|safe }}','{{ jsSdkUrl|safe }}',{{ config|to_json|safe }},{{ isLazy|safe|lower }});
{% load sentry_helpers %}!function(n,e,r,t,i,o,a,c,u){for(var s=u,f=0;f<document.scripts.length;f++)if(document.scripts[f].src.indexOf(o)>-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<p.length;r++)"function"==typeof p[r]&&p[r]();for(r=0;r<v.length;r++){_(o=v[r])&&"init"===o.f&&e.init.apply(e,o.a)}R()||e.init();var t=n.onerror,i=n.onunhandledrejection;for(r=0;r<v.length;r++){var o;if(_(o=v[r])){if("init"===o.f)continue;e[o.f].apply(e,o.a)}else l(o)&&t?t.apply(n,o.e):d(o)&&i&&i.apply(n,[o.p])}}catch(n){console.error(n)}}(e)}))}catch(n){console.error(n)}}var g=!1;function O(){if(!g){g=!0;var n=e.scripts[0],r=e.createElement("script");r.src=a,r.crossOrigin="anonymous",r.addEventListener("load",m,{once:!0,passive:!0}),n.parentNode.insertBefore(r,n)}}function R(){var e=n.__SENTRY__;return!(void 0===e||!e.hub||!e.hub.getClient())}n[i]=n[i]||{},n[i].onLoad=function(n){R()?n():p.push(n)},n[i].forceLoad=function(){setTimeout((function(){O()}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(e){n[i][e]=function(){h({f:e,a:arguments})}})),n.addEventListener(r,y),n.addEventListener(t,E),s||setTimeout((function(){O()}))}(window,document,"error","unhandledrejection","Sentry",'{{ publicKey|safe }}','{{ jsSdkUrl|safe }}',{{ config|to_json|safe }},{{ isLazy|safe|lower }});
Loading