|
1 | | -{% load sentry_helpers %}(function sentryLoader(_window, _document, _errorEvent, _unhandledrejectionEvent, _namespace, _publicKey, _sdkBundleUrl, _config, _lazy) { |
| 1 | +{% load sentry_helpers %}(function sentryLoader(_window, _document, _errorEvent, _unhandledrejectionEvent, _namespace, _publicKey, _sdkBundleUrl, _loaderInitConfig, _lazy) { |
2 | 2 | var lazy = _lazy; |
3 | | - var forceLoad = false; |
4 | 3 | for (var i = 0; i < document.scripts.length; i++) { |
5 | 4 | if (document.scripts[i].src.indexOf(_publicKey) > -1) { |
6 | 5 | // If lazy was set to true above, we need to check if the user has set data-lazy="no" |
|
11 | 10 | break; |
12 | 11 | } |
13 | 12 | } |
14 | | - var injected = false; |
15 | 13 | var onLoadCallbacks = []; |
| 14 | + function queueIsError(item) { |
| 15 | + return 'e' in item; |
| 16 | + } |
| 17 | + function queueIsPromiseRejection(item) { |
| 18 | + return 'p' in item; |
| 19 | + } |
| 20 | + function queueIsFunction(item) { |
| 21 | + return 'f' in item; |
| 22 | + } |
| 23 | + var queue = []; |
16 | 24 | // Create a namespace and attach function that will store captured exception |
17 | 25 | // Because functions are also objects, we can attach the queue itself straight to it and save some bytes |
18 | | - var queue = function (content) { |
19 | | - // content.e = error |
20 | | - // content.p = promise rejection |
21 | | - // content.f = function call the Sentry |
22 | | - if (('e' in content || |
23 | | - 'p' in content || |
24 | | - (content.f && content.f.indexOf('capture') > -1) || |
25 | | - (content.f && content.f.indexOf('showReportDialog') > -1)) && |
26 | | - lazy) { |
| 26 | + function enqueue(item) { |
| 27 | + if (lazy && |
| 28 | + (queueIsError(item) || |
| 29 | + queueIsPromiseRejection(item) || |
| 30 | + (queueIsFunction(item) && item.f.indexOf('capture') > -1) || |
| 31 | + (queueIsFunction(item) && item.f.indexOf('showReportDialog') > -1))) { |
27 | 32 | // We only want to lazy inject/load the sdk bundle if |
28 | 33 | // an error or promise rejection occured |
29 | 34 | // OR someone called `capture...` on the SDK |
30 | | - injectSdk(onLoadCallbacks); |
| 35 | + injectCDNScriptTag(); |
31 | 36 | } |
32 | | - queue.data.push(content); |
33 | | - }; |
34 | | - queue.data = []; |
| 37 | + queue.push(item); |
| 38 | + } |
35 | 39 | function onError() { |
36 | 40 | // Use keys as "data type" to save some characters" |
37 | | - queue({ |
| 41 | + enqueue({ |
38 | 42 | e: [].slice.call(arguments), |
39 | 43 | }); |
40 | 44 | } |
41 | 45 | function onUnhandledRejection(e) { |
42 | | - queue({ |
| 46 | + enqueue({ |
43 | 47 | p: 'reason' in e |
44 | 48 | ? e.reason |
45 | 49 | : 'detail' in e && 'reason' in e.detail |
46 | 50 | ? e.detail.reason |
47 | 51 | : e, |
48 | 52 | }); |
49 | 53 | } |
50 | | - function injectSdk(callbacks) { |
51 | | - if (injected) { |
| 54 | + function onSentryCDNScriptLoaded() { |
| 55 | + try { |
| 56 | + // Add loader as SDK source |
| 57 | + _window.SENTRY_SDK_SOURCE = 'loader'; |
| 58 | + var SDK_1 = _window[_namespace]; |
| 59 | + var cdnInit_1 = SDK_1.init; |
| 60 | + // Configure it using provided DSN and config object |
| 61 | + SDK_1.init = function (options) { |
| 62 | + // Remove the lazy mode error event listeners that we previously registered |
| 63 | + // Once we call init, we can assume that Sentry has added it's own global error listeners |
| 64 | + _window.removeEventListener(_errorEvent, onError); |
| 65 | + _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); |
| 66 | + var mergedInitOptions = _loaderInitConfig; |
| 67 | + for (var key in options) { |
| 68 | + if (Object.prototype.hasOwnProperty.call(options, key)) { |
| 69 | + mergedInitOptions[key] = options[key]; |
| 70 | + } |
| 71 | + } |
| 72 | + setupDefaultIntegrations(mergedInitOptions, SDK_1); |
| 73 | + cdnInit_1(mergedInitOptions); |
| 74 | + }; |
| 75 | + // Wait a tick to ensure that all `Sentry.onLoad()` callbacks have been registered |
| 76 | + setTimeout(function () { return setupSDK(SDK_1); }); |
| 77 | + } |
| 78 | + catch (o_O) { |
| 79 | + console.error(o_O); |
| 80 | + } |
| 81 | + } |
| 82 | + var injectedCDNScriptTag = false; |
| 83 | + /** |
| 84 | + * Injects script tag into the page pointing to the CDN bundle. |
| 85 | + */ |
| 86 | + function injectCDNScriptTag() { |
| 87 | + if (injectedCDNScriptTag) { |
52 | 88 | return; |
53 | 89 | } |
54 | | - injected = true; |
| 90 | + injectedCDNScriptTag = true; |
55 | 91 | // Create a `script` tag with provided SDK `url` and attach it just before the first, already existing `script` tag |
56 | 92 | // Scripts that are dynamically created and added to the document are async by default, |
57 | 93 | // they don't block rendering and execute as soon as they download, meaning they could |
58 | 94 | // come out in the wrong order. Because of that we don't need async=1 as GA does. |
59 | 95 | // it was probably(?) a legacy behavior that they left to not modify few years old snippet |
60 | 96 | // https://www.html5rocks.com/en/tutorials/speed/script-loading/ |
61 | | - var _currentScriptTag = _document.scripts[0]; |
62 | | - var _newScriptTag = _document.createElement('script'); |
63 | | - _newScriptTag.src = _sdkBundleUrl; |
64 | | - _newScriptTag.crossOrigin = 'anonymous'; |
| 97 | + var firstScriptTagInDom = _document.scripts[0]; |
| 98 | + var cdnScriptTag = _document.createElement('script'); |
| 99 | + cdnScriptTag.src = _sdkBundleUrl; |
| 100 | + cdnScriptTag.crossOrigin = 'anonymous'; |
65 | 101 | // Once our SDK is loaded |
66 | | - _newScriptTag.addEventListener('load', function () { |
67 | | - try { |
68 | | - _window.removeEventListener(_errorEvent, onError); |
69 | | - _window.removeEventListener(_unhandledrejectionEvent, onUnhandledRejection); |
70 | | - // Add loader as SDK source |
71 | | - _window.SENTRY_SDK_SOURCE = 'loader'; |
72 | | - var SDK_1 = _window[_namespace]; |
73 | | - var oldInit_1 = SDK_1.init; |
74 | | - // Configure it using provided DSN and config object |
75 | | - SDK_1.init = function (options) { |
76 | | - var target = _config; |
77 | | - for (var key in options) { |
78 | | - if (Object.prototype.hasOwnProperty.call(options, key)) { |
79 | | - target[key] = options[key]; |
80 | | - } |
81 | | - } |
82 | | - setupDefaultIntegrations(target, SDK_1); |
83 | | - oldInit_1(target); |
84 | | - }; |
85 | | - sdkLoaded(callbacks, SDK_1); |
86 | | - } |
87 | | - catch (o_O) { |
88 | | - console.error(o_O); |
89 | | - } |
| 102 | + cdnScriptTag.addEventListener('load', onSentryCDNScriptLoaded, { |
| 103 | + once: true, |
| 104 | + passive: true, |
90 | 105 | }); |
91 | | - _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); |
| 106 | + firstScriptTagInDom.parentNode.insertBefore(cdnScriptTag, firstScriptTagInDom); |
92 | 107 | } |
93 | 108 | // We want to ensure to only add default integrations if they haven't been added by the user. |
94 | 109 | function setupDefaultIntegrations(config, SDK) { |
|
115 | 130 | __sentry.hub && |
116 | 131 | __sentry.hub.getClient()); |
117 | 132 | } |
118 | | - function sdkLoaded(callbacks, SDK) { |
| 133 | + function setupSDK(SDK) { |
119 | 134 | try { |
120 | 135 | // We have to make sure to call all callbacks first |
121 | | - for (var i = 0; i < callbacks.length; i++) { |
122 | | - if (typeof callbacks[i] === 'function') { |
123 | | - callbacks[i](); |
| 136 | + for (var i = 0; i < onLoadCallbacks.length; i++) { |
| 137 | + if (typeof onLoadCallbacks[i] === 'function') { |
| 138 | + onLoadCallbacks[i](); |
124 | 139 | } |
125 | 140 | } |
126 | | - var data = queue.data; |
127 | | - var initAlreadyCalled = sdkIsLoaded(); |
128 | | - // Call init first, if provided |
129 | | - data.sort(function (a) { return (a.f === 'init' ? -1 : 0); }); |
130 | | - // We want to replay all calls to Sentry and also make sure that `init` is called if it wasn't already |
131 | | - // We replay all calls to `Sentry.*` now |
132 | | - var calledSentry = false; |
133 | | - for (var i = 0; i < data.length; i++) { |
134 | | - if (data[i].f) { |
135 | | - calledSentry = true; |
136 | | - var call = data[i]; |
137 | | - if (initAlreadyCalled === false && call.f !== 'init') { |
138 | | - // First call always has to be init, this is a conveniece for the user so call to init is optional |
139 | | - SDK.init(); |
140 | | - } |
141 | | - initAlreadyCalled = true; |
142 | | - SDK[call.f].apply(SDK, call.a); |
| 141 | + // First call all inits from the queue |
| 142 | + for (var i = 0; i < queue.length; i++) { |
| 143 | + var item = queue[i]; |
| 144 | + if (queueIsFunction(item) && item.f === 'init') { |
| 145 | + SDK.init.apply(SDK, item.a); |
143 | 146 | } |
144 | 147 | } |
145 | | - if (initAlreadyCalled === false && calledSentry === false) { |
146 | | - // Sentry has never been called but we need Sentry.init() so call it |
| 148 | + // If the SDK has not been called manually, either in an onLoad callback, or somewhere else, |
| 149 | + // we initialize it for the user |
| 150 | + if (!sdkIsLoaded()) { |
147 | 151 | SDK.init(); |
148 | 152 | } |
149 | | - // Because we installed the SDK, at this point we have an access to TraceKit's handler, |
| 153 | + // Now, we _know_ that the SDK is initialized, and can continue with the rest of the queue |
| 154 | + // Because we installed the SDK, at this point we can assume that the global handlers have been patched |
150 | 155 | // which can take care of browser differences (eg. missing exception argument in onerror) |
151 | | - var tracekitErrorHandler = _window.onerror; |
152 | | - var tracekitUnhandledRejectionHandler = _window.onunhandledrejection; |
153 | | - // And now capture all previously caught exceptions |
154 | | - for (var i = 0; i < data.length; i++) { |
155 | | - if ('e' in data[i] && tracekitErrorHandler) { |
156 | | - tracekitErrorHandler.apply(_window, data[i].e); |
| 156 | + var sentryPatchedErrorHandler = _window.onerror; |
| 157 | + var sentryPatchedUnhandledRejectionHandler = _window.onunhandledrejection; |
| 158 | + for (var i = 0; i < queue.length; i++) { |
| 159 | + var item = queue[i]; |
| 160 | + if (queueIsFunction(item)) { |
| 161 | + // We already called all init before, so just skip this |
| 162 | + if (item.f === 'init') { |
| 163 | + continue; |
| 164 | + } |
| 165 | + SDK[item.f].apply(SDK, item.a); |
| 166 | + } |
| 167 | + else if (queueIsError(item) && sentryPatchedErrorHandler) { |
| 168 | + sentryPatchedErrorHandler.apply(_window, item.e); |
157 | 169 | } |
158 | | - else if ('p' in data[i] && tracekitUnhandledRejectionHandler) { |
159 | | - tracekitUnhandledRejectionHandler.apply(_window, [data[i].p]); |
| 170 | + else if (queueIsPromiseRejection(item) && |
| 171 | + sentryPatchedUnhandledRejectionHandler) { |
| 172 | + sentryPatchedUnhandledRejectionHandler.apply(_window, [item.p]); |
160 | 173 | } |
161 | 174 | } |
162 | 175 | } |
|
167 | 180 | // We make sure we do not overwrite window.Sentry since there could be already integrations in there |
168 | 181 | _window[_namespace] = _window[_namespace] || {}; |
169 | 182 | _window[_namespace].onLoad = function (callback) { |
170 | | - onLoadCallbacks.push(callback); |
171 | | - if (lazy && !forceLoad) { |
| 183 | + // If the SDK was already loaded, call the callback immediately |
| 184 | + if (sdkIsLoaded()) { |
| 185 | + callback(); |
172 | 186 | return; |
173 | 187 | } |
174 | | - injectSdk(onLoadCallbacks); |
| 188 | + onLoadCallbacks.push(callback); |
175 | 189 | }; |
176 | 190 | _window[_namespace].forceLoad = function () { |
177 | | - forceLoad = true; |
178 | | - if (lazy) { |
179 | | - setTimeout(function () { |
180 | | - injectSdk(onLoadCallbacks); |
181 | | - }); |
182 | | - } |
| 191 | + setTimeout(function () { |
| 192 | + injectCDNScriptTag(); |
| 193 | + }); |
183 | 194 | }; |
184 | 195 | [ |
185 | 196 | 'init', |
|
192 | 203 | 'showReportDialog', |
193 | 204 | ].forEach(function (f) { |
194 | 205 | _window[_namespace][f] = function () { |
195 | | - queue({ f: f, a: arguments }); |
| 206 | + enqueue({ f: f, a: arguments }); |
196 | 207 | }; |
197 | 208 | }); |
198 | 209 | _window.addEventListener(_errorEvent, onError); |
199 | 210 | _window.addEventListener(_unhandledrejectionEvent, onUnhandledRejection); |
200 | 211 | if (!lazy) { |
201 | 212 | setTimeout(function () { |
202 | | - injectSdk(onLoadCallbacks); |
| 213 | + injectCDNScriptTag(); |
203 | 214 | }); |
204 | 215 | } |
205 | 216 | })(window, document, 'error', 'unhandledrejection', 'Sentry', '{{ publicKey|safe }}', '{{ jsSdkUrl|safe }}', {{ config|to_json|safe }}, {{ isLazy|safe|lower }}); |
0 commit comments