Skip to content

Commit 1d31662

Browse files
authored
feat: Add loader + integration tests (#1559)
1 parent aca120c commit 1d31662

File tree

10 files changed

+1797
-1338
lines changed

10 files changed

+1797
-1338
lines changed

packages/browser/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@
5858
"fix": "run-s fix:tslint fix:prettier",
5959
"fix:prettier": "prettier --write '{src,test}/**/*.ts'",
6060
"fix:tslint": "tslint --fix -t stylish -p .",
61-
"test": "karma start karma/karma.unit.config.js",
62-
"test:watch": "karma start karma/karma.unit.config.js --auto-watch --no-single-run",
63-
"test:integration": "karma start karma/karma.integration.config.js",
64-
"test:integration:watch": "karma start karma/karma.integration.config.js --auto-watch --no-single-run",
61+
"test": "karma start test/karma/karma.unit.config.js",
62+
"test:watch": "karma start test/karma/karma.unit.config.js --auto-watch --no-single-run",
63+
"test:integration": "karma start test/karma/karma.integration.config.js",
64+
"test:integration:watch": "karma start test/karma/karma.integration.config.js --auto-watch --no-single-run",
6565
"size:check": "cat build/bundle.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print $1,\"kB\";}'",
6666
"version": "node ../../scripts/versionbump.js src/version.ts"
6767
},

packages/browser/src/loader.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
(function(
2+
_window,
3+
_document,
4+
_script,
5+
_onerror,
6+
_onunhandledrejection,
7+
_namespace,
8+
_publicKey,
9+
_sdkBundleUrl,
10+
_config,
11+
) {
12+
var lazy = true;
13+
var forceLoad = false;
14+
15+
for (var i = 0; i < document.scripts.length; i++) {
16+
if (document.scripts[i].src.indexOf(_publicKey) > -1) {
17+
lazy = !(document.scripts[i].dataset.lazy === 'no');
18+
break;
19+
}
20+
}
21+
22+
var injected = false;
23+
var onLoadCallback;
24+
25+
// Create a namespace and attach function that will store captured exception
26+
// Because functions are also objects, we can attach the queue itself straight to it and save some bytes
27+
var queue = function(content) {
28+
// content.e = error
29+
// content.p = promise rejection
30+
// content.f = function call the Sentry
31+
if (
32+
(content.e ||
33+
content.p ||
34+
(content.f && content.f.indexOf('capture') > -1) ||
35+
(content.f && content.f.indexOf('showReportDialog') > -1)) &&
36+
lazy
37+
) {
38+
// We only want to lazy inject/load the sdk bundle if
39+
// an error or promise rejection occured
40+
// OR someone called `capture...` on the SDK
41+
injectSdk(onLoadCallback);
42+
}
43+
queue.data.push(content);
44+
};
45+
queue.data = [];
46+
47+
function injectSdk(callback) {
48+
if (injected) {
49+
return;
50+
}
51+
injected = true;
52+
53+
// Create a `script` tag with provided SDK `url` and attach it just before the first, already existing `script` tag
54+
// Scripts that are dynamically created and added to the document are async by default,
55+
// they don't block rendering and execute as soon as they download, meaning they could
56+
// come out in the wrong order. Because of that we don't need async=1 as GA does.
57+
// it was probably(?) a legacy behavior that they left to not modify few years old snippet
58+
// https://www.html5rocks.com/en/tutorials/speed/script-loading/
59+
var _currentScriptTag = _document.getElementsByTagName(_script)[0];
60+
var _newScriptTag = _document.createElement(_script);
61+
_newScriptTag.src = _sdkBundleUrl;
62+
_newScriptTag.crossorigin = 'anonymous';
63+
64+
// Once our SDK is loaded
65+
_newScriptTag.addEventListener('load', function() {
66+
try {
67+
// Restore onerror/onunhandledrejection handlers
68+
_window[_onerror] = _oldOnerror;
69+
_window[_onunhandledrejection] = _oldOnunhandledrejection;
70+
71+
var SDK = _window[_namespace];
72+
73+
var oldInit = SDK.init;
74+
75+
// Configure it using provided DSN and config object
76+
SDK.init = function(options) {
77+
var target = _config;
78+
for (var key in options) {
79+
if (Object.prototype.hasOwnProperty.call(options, key)) {
80+
target[key] = options[key];
81+
}
82+
}
83+
oldInit(target);
84+
};
85+
86+
sdkLoaded(callback, SDK);
87+
} catch (o_O) {
88+
console.error(o_O);
89+
}
90+
});
91+
92+
_currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag);
93+
}
94+
95+
function sdkLoaded(callback, SDK) {
96+
try {
97+
if (callback) {
98+
callback();
99+
}
100+
var data = queue.data;
101+
102+
// We want to replay all calls to Sentry first to make sure init is called before
103+
// we call all our internal error handlers
104+
var firstInitCall = false;
105+
var calledSentry = false;
106+
for (var i = 0; i < data.length; i++) {
107+
if (data[i].f) {
108+
calledSentry = true;
109+
var call = data[i];
110+
if (firstInitCall === false && call.f !== 'init') {
111+
// First call always has to be init, this is a conveniece for the user
112+
// so call to init is optional
113+
SDK.init();
114+
}
115+
firstInitCall = true;
116+
SDK[call.f].apply(SDK, call.a);
117+
}
118+
}
119+
if (calledSentry === false) {
120+
// Sentry has never been called but we need Sentry.init() so call it
121+
SDK.init();
122+
}
123+
// Because we installed the SDK, at this point we have an access to TraceKit's handler,
124+
// which can take care of browser differences (eg. missing exception argument in onerror)
125+
var tracekitErrorHandler = _window[_onerror];
126+
127+
// And now capture all previously caught exceptions
128+
for (var i = 0; i < data.length; i++) {
129+
if (data[i].e) {
130+
tracekitErrorHandler.apply(_window, data[i].e);
131+
} else if (data[i].p) {
132+
SDK.captureException(data[i].p);
133+
}
134+
}
135+
} catch (o_O) {
136+
console.error(o_O);
137+
}
138+
}
139+
140+
// We don't want to _window.Sentry = _window.Sentry || { ... } since we want to make sure
141+
// that the first Sentry "instance" is our with onLoad
142+
_window[_namespace] = {
143+
onLoad: function(callback) {
144+
if (lazy && !forceLoad) {
145+
onLoadCallback = callback;
146+
} else {
147+
injectSdk(callback);
148+
}
149+
},
150+
forceLoad: function() {
151+
forceLoad = true;
152+
if (lazy) {
153+
setTimeout(function() {
154+
injectSdk(onLoadCallback);
155+
});
156+
}
157+
},
158+
};
159+
160+
[
161+
'init',
162+
'addBreadcrumb',
163+
'captureMessage',
164+
'captureException',
165+
'captureEvent',
166+
'configureScope',
167+
'withScope',
168+
'showReportDialog',
169+
].forEach(function(f) {
170+
_window[_namespace][f] = function() {
171+
queue({ f: f, a: arguments });
172+
};
173+
});
174+
175+
// Store reference to the old `onerror` handler and override it with our own function
176+
// that will just push exceptions to the queue and call through old handler if we found one
177+
var _oldOnerror = _window[_onerror];
178+
_window[_onerror] = function(message, source, lineno, colno, exception) {
179+
// Use keys as "data type" to save some characters"
180+
queue({
181+
e: [].slice.call(arguments),
182+
});
183+
184+
if (_oldOnerror) _oldOnerror.apply(_window, arguments);
185+
};
186+
187+
// Do the same store/queue/call operations for `onunhandledrejection` event
188+
var _oldOnunhandledrejection = _window[_onunhandledrejection];
189+
_window[_onunhandledrejection] = function(exception) {
190+
queue({
191+
p: exception.reason,
192+
});
193+
if (_oldOnunhandledrejection) _oldOnunhandledrejection.apply(_window, arguments);
194+
};
195+
196+
if (!lazy) {
197+
setTimeout(function() {
198+
injectSdk(onLoadCallback);
199+
});
200+
}
201+
})(window, document, 'script', 'onerror', 'onunhandledrejection', 'Sentry', 'loader.js', '../../build/bundle.js', {
202+
dsn: 'https://[email protected]/1',
203+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
3+
*
4+
* http://paulirish.com/2011/requestanimationframe-for-smart-animating/
5+
* http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
6+
*
7+
* MIT license
8+
*/
9+
(function() {
10+
var lastTime = 0;
11+
var vendors = ['ms', 'moz', 'webkit', 'o'];
12+
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
13+
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
14+
window.cancelAnimationFrame =
15+
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
16+
}
17+
18+
if (!window.requestAnimationFrame)
19+
window.requestAnimationFrame = function(callback, element) {
20+
var currTime = new Date().getTime();
21+
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
22+
var id = window.setTimeout(function() {
23+
callback(currTime + timeToCall);
24+
}, timeToCall);
25+
lastTime = currTime + timeToCall;
26+
return id;
27+
};
28+
29+
if (!window.cancelAnimationFrame)
30+
window.cancelAnimationFrame = function(id) {
31+
clearTimeout(id);
32+
};
33+
})();
34+
35+
/**
36+
* DOM4 MouseEvent and KeyboardEvent Polyfills
37+
*
38+
* References:
39+
* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
40+
* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent#Polyfill
41+
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
42+
*/
43+
(function() {
44+
try {
45+
new MouseEvent('click');
46+
return false; // No need to polyfill
47+
} catch (e) {
48+
// Need to polyfill - fall through
49+
}
50+
51+
var MouseEvent = function(eventType) {
52+
var mouseEvent = document.createEvent('MouseEvent');
53+
mouseEvent.initMouseEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
54+
return mouseEvent;
55+
};
56+
57+
MouseEvent.prototype = Event.prototype;
58+
window.MouseEvent = MouseEvent;
59+
})();
60+
61+
(function() {
62+
try {
63+
new KeyboardEvent('keypress');
64+
return false; // No need to polyfill
65+
} catch (e) {
66+
// Need to polyfill - fall through
67+
}
68+
69+
var KeyboardEvent = function(eventType) {
70+
var keyboardEvent = document.createEvent('KeyboardEvent');
71+
if (keyboardEvent.initKeyboardEvent)
72+
keyboardEvent.initKeyboardEvent(eventType, true, true, window, false, false, false, false, 'a', 0);
73+
if (keyboardEvent.initKeyEvent)
74+
keyboardEvent.initKeyEvent(eventType, true, true, window, false, false, false, false, 'a');
75+
return keyboardEvent;
76+
};
77+
78+
KeyboardEvent.prototype = Event.prototype;
79+
window.KeyboardEvent = KeyboardEvent;
80+
})();

0 commit comments

Comments
 (0)