-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
loader.js
217 lines (191 loc) · 6.99 KB
/
loader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// prettier-ignore
// Prettier disabled due to trailing comma not working in IE10/11
(function(
_window,
_document,
_script,
_onerror,
_onunhandledrejection,
_namespace,
_publicKey,
_sdkBundleUrl,
_config
) {
var lazy = true;
var forceLoad = false;
for (var i = 0; i < document.scripts.length; i++) {
if (document.scripts[i].src.indexOf(_publicKey) > -1) {
lazy = !(document.scripts[i].getAttribute('data-lazy') === 'no');
break;
}
}
var injected = false;
var onLoadCallbacks = [];
// 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
) {
// 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);
}
queue.data.push(content);
};
queue.data = [];
function injectSdk(callbacks) {
if (injected) {
return;
}
injected = 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.getElementsByTagName(_script)[0];
var _newScriptTag = _document.createElement(_script);
_newScriptTag.src = _sdkBundleUrl;
_newScriptTag.setAttribute('crossorigin', 'anonymous');
// Once our SDK is loaded
_newScriptTag.addEventListener('load', function() {
try {
// Restore onerror/onunhandledrejection handlers
_window[_onerror] = _oldOnerror;
_window[_onunhandledrejection] = _oldOnunhandledrejection;
var SDK = _window[_namespace];
var oldInit = SDK.init;
// Configure it using provided DSN and config object
SDK.init = function(options) {
var target = _config;
for (var key in options) {
if (Object.prototype.hasOwnProperty.call(options, key)) {
target[key] = options[key];
}
}
oldInit(target);
};
sdkLoaded(callbacks, SDK);
} catch (o_O) {
console.error(o_O);
}
});
_currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag);
}
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') {
callbacks[i]();
}
}
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;
}
// 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);
}
}
if (initAlreadyCalled === false && calledSentry === false) {
// Sentry has never been called but we need Sentry.init() so call it
SDK.init();
}
// 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)
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);
} else if ('p' in data[i] && tracekitUnhandledRejectionHandler) {
tracekitUnhandledRejectionHandler.apply(_window, [data[i].p]);
}
}
} catch (o_O) {
console.error(o_O);
}
}
// 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) {
return;
}
injectSdk(onLoadCallbacks);
};
_window[_namespace].forceLoad = function() {
forceLoad = true;
if (lazy) {
setTimeout(function() {
injectSdk(onLoadCallbacks);
});
}
};
[
'init',
'addBreadcrumb',
'captureMessage',
'captureException',
'captureEvent',
'configureScope',
'withScope',
'showReportDialog'
].forEach(function(f) {
_window[_namespace][f] = function() {
queue({ f: f, a: arguments });
};
});
// 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) {
// Use keys as "data type" to save some characters"
queue({
e: [].slice.call(arguments)
});
if (_oldOnerror) _oldOnerror.apply(_window, arguments);
};
// Do the same store/queue/call operations for `onunhandledrejection` event
var _oldOnunhandledrejection = _window[_onunhandledrejection];
_window[_onunhandledrejection] = function(e) {
queue({
p: 'reason' in e ? e.reason : 'detail' in e && 'reason' in e.detail ? e.detail.reason : e
});
if (_oldOnunhandledrejection) _oldOnunhandledrejection.apply(_window, arguments);
};
if (!lazy) {
setTimeout(function () {
injectSdk(onLoadCallbacks);
});
}
})(window, document, 'script', 'onerror', 'onunhandledrejection', 'Sentry', 'loader.js', '../../build/bundle.js', {
dsn: 'https://[email protected]/1'
});