ravenjs-gem 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/README.md +3 -3
- data/Rakefile +3 -3
- data/lib/ravenjs-gem/version.rb +1 -1
- data/vendor/assets/javascripts/1.1.16/raven.js +1838 -0
- data/vendor/assets/javascripts/raven.js +65 -52
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzM2M2JmYjQwMDdlZDkxZGFjOTQxMjVhNDY1OTMzODY2NzY3MWNkMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Mjg0ODI4MjE2ODA0NjUyMWM1MWE0ZWEyMGMxNTZjMjUzOWNkNDVmOA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjE5NTAxN2Y2MWY2NmNlZDhkMDZjYTJmNjBkNWZjYjVmZmM0ODUzYzJkODIy
|
10
|
+
MjJjYmMzOGExMmRhMmI3OWEzOTkxYTQ3NTY2Mzk3YjlhNTZmMjBmYmQwOTk5
|
11
|
+
MDJjMmE0MzE0ZTk0YjA2Yjk5NDM1NGQxMWM5NTY5ZmFjZmNjNDA=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MjU3ODZmOTZjYjY2NmY5ODk4MzE3ZTg5YjU4MWEwOTMyMDVlMDE0NTQ0NjA0
|
14
|
+
NzhiMmYyYzNjNWIyZTJjYjMxODk1N2VlNTI2OWU5Mzk2ZTc2MzlhODUzM2U5
|
15
|
+
Mjg5MjlhYmU5OWUxOTcwYjBlMjkzMTliYWE3ZmE4YTNjZTM2MmM=
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Raven.js Gem
|
2
|
-
[](http://badge.fury.io/rb/ravenjs-gem) [](https://gemnasium.com/ets-berkeley-edu/ravenjs-gem) [](https://codeclimate.com/github/ets-berkeley-edu/ravenjs-gem)
|
3
3
|
|
4
4
|
[Raven.js][ravenjs] as a Ruby gem.
|
5
5
|
|
@@ -41,8 +41,8 @@ If you would like to update this gem you should take the following steps:
|
|
41
41
|
Then the maintainer of the gem will need to do the following steps:
|
42
42
|
|
43
43
|
1. Update the version [lib/ravenjs-gem/version.rb](lib/ravenjs-gem/version.rb)
|
44
|
-
1. Run
|
44
|
+
1. Run `gem build ravenjs-gem.gemspec` to package the gem
|
45
45
|
1. Once satisfied, push the gem up to RubyGems.org with ``gem push ravenjs-gem-<VERSION>.gem``
|
46
46
|
1. Update [the changelog](CHANGELOG.md)
|
47
47
|
|
48
|
-
[ravenjs]: https://github.com/getsentry/raven-js
|
48
|
+
[ravenjs]: https://github.com/getsentry/raven-js
|
data/Rakefile
CHANGED
@@ -64,15 +64,15 @@ task :download do |t|
|
|
64
64
|
create_dir dir_js, get_version
|
65
65
|
|
66
66
|
# Download the right files
|
67
|
-
url_root = 'https://raw.github.com/getsentry/raven-js/' + commit + '/dist/'
|
67
|
+
url_root = 'https://raw.github.com/getsentry/raven-js/' + commit + '/dist/'
|
68
68
|
url_js = url_root + 'raven.js'
|
69
69
|
url_js_min = url_root + 'raven.min.js'
|
70
70
|
url_js_map = url_root + 'raven.min.map'
|
71
71
|
|
72
72
|
puts "Downloading... \n"
|
73
73
|
download_file url_js, dir_js, 'raven.js', get_version
|
74
|
-
download_file url_js_min, dir_js, 'raven.min.js', get_version
|
75
|
-
download_file url_js_map, dir_js, 'raven.min.map', get_version
|
74
|
+
# download_file url_js_min, dir_js, 'raven.min.js', get_version
|
75
|
+
# download_file url_js_map, dir_js, 'raven.min.map', get_version
|
76
76
|
|
77
77
|
end
|
78
78
|
|
data/lib/ravenjs-gem/version.rb
CHANGED
@@ -0,0 +1,1838 @@
|
|
1
|
+
/*! Raven.js 1.1.16 (463f68f) | github.com/getsentry/raven-js */
|
2
|
+
|
3
|
+
/*
|
4
|
+
* Includes TraceKit
|
5
|
+
* https://github.com/getsentry/TraceKit
|
6
|
+
*
|
7
|
+
* Copyright 2014 Matt Robenolt and other contributors
|
8
|
+
* Released under the BSD license
|
9
|
+
* https://github.com/getsentry/raven-js/blob/master/LICENSE
|
10
|
+
*
|
11
|
+
*/
|
12
|
+
;(function(window, undefined){
|
13
|
+
'use strict';
|
14
|
+
|
15
|
+
/*
|
16
|
+
TraceKit - Cross brower stack traces - github.com/occ/TraceKit
|
17
|
+
MIT license
|
18
|
+
*/
|
19
|
+
|
20
|
+
var TraceKit = {
|
21
|
+
remoteFetching: false,
|
22
|
+
collectWindowErrors: true,
|
23
|
+
// 3 lines before, the offending line, 3 lines after
|
24
|
+
linesOfContext: 7
|
25
|
+
};
|
26
|
+
|
27
|
+
// global reference to slice
|
28
|
+
var _slice = [].slice;
|
29
|
+
var UNKNOWN_FUNCTION = '?';
|
30
|
+
|
31
|
+
|
32
|
+
/**
|
33
|
+
* TraceKit.wrap: Wrap any function in a TraceKit reporter
|
34
|
+
* Example: func = TraceKit.wrap(func);
|
35
|
+
*
|
36
|
+
* @param {Function} func Function to be wrapped
|
37
|
+
* @return {Function} The wrapped func
|
38
|
+
*/
|
39
|
+
TraceKit.wrap = function traceKitWrapper(func) {
|
40
|
+
function wrapped() {
|
41
|
+
try {
|
42
|
+
return func.apply(this, arguments);
|
43
|
+
} catch (e) {
|
44
|
+
TraceKit.report(e);
|
45
|
+
throw e;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
return wrapped;
|
49
|
+
};
|
50
|
+
|
51
|
+
/**
|
52
|
+
* TraceKit.report: cross-browser processing of unhandled exceptions
|
53
|
+
*
|
54
|
+
* Syntax:
|
55
|
+
* TraceKit.report.subscribe(function(stackInfo) { ... })
|
56
|
+
* TraceKit.report.unsubscribe(function(stackInfo) { ... })
|
57
|
+
* TraceKit.report(exception)
|
58
|
+
* try { ...code... } catch(ex) { TraceKit.report(ex); }
|
59
|
+
*
|
60
|
+
* Supports:
|
61
|
+
* - Firefox: full stack trace with line numbers, plus column number
|
62
|
+
* on top frame; column number is not guaranteed
|
63
|
+
* - Opera: full stack trace with line and column numbers
|
64
|
+
* - Chrome: full stack trace with line and column numbers
|
65
|
+
* - Safari: line and column number for the top frame only; some frames
|
66
|
+
* may be missing, and column number is not guaranteed
|
67
|
+
* - IE: line and column number for the top frame only; some frames
|
68
|
+
* may be missing, and column number is not guaranteed
|
69
|
+
*
|
70
|
+
* In theory, TraceKit should work on all of the following versions:
|
71
|
+
* - IE5.5+ (only 8.0 tested)
|
72
|
+
* - Firefox 0.9+ (only 3.5+ tested)
|
73
|
+
* - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
|
74
|
+
* Exceptions Have Stacktrace to be enabled in opera:config)
|
75
|
+
* - Safari 3+ (only 4+ tested)
|
76
|
+
* - Chrome 1+ (only 5+ tested)
|
77
|
+
* - Konqueror 3.5+ (untested)
|
78
|
+
*
|
79
|
+
* Requires TraceKit.computeStackTrace.
|
80
|
+
*
|
81
|
+
* Tries to catch all unhandled exceptions and report them to the
|
82
|
+
* subscribed handlers. Please note that TraceKit.report will rethrow the
|
83
|
+
* exception. This is REQUIRED in order to get a useful stack trace in IE.
|
84
|
+
* If the exception does not reach the top of the browser, you will only
|
85
|
+
* get a stack trace from the point where TraceKit.report was called.
|
86
|
+
*
|
87
|
+
* Handlers receive a stackInfo object as described in the
|
88
|
+
* TraceKit.computeStackTrace docs.
|
89
|
+
*/
|
90
|
+
TraceKit.report = (function reportModuleWrapper() {
|
91
|
+
var handlers = [],
|
92
|
+
lastArgs = null,
|
93
|
+
lastException = null,
|
94
|
+
lastExceptionStack = null;
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Add a crash handler.
|
98
|
+
* @param {Function} handler
|
99
|
+
*/
|
100
|
+
function subscribe(handler) {
|
101
|
+
installGlobalHandler();
|
102
|
+
handlers.push(handler);
|
103
|
+
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* Remove a crash handler.
|
107
|
+
* @param {Function} handler
|
108
|
+
*/
|
109
|
+
function unsubscribe(handler) {
|
110
|
+
for (var i = handlers.length - 1; i >= 0; --i) {
|
111
|
+
if (handlers[i] === handler) {
|
112
|
+
handlers.splice(i, 1);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Remove all crash handlers.
|
119
|
+
*/
|
120
|
+
function unsubscribeAll() {
|
121
|
+
uninstallGlobalHandler();
|
122
|
+
handlers = [];
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Dispatch stack information to all handlers.
|
127
|
+
* @param {Object.<string, *>} stack
|
128
|
+
*/
|
129
|
+
function notifyHandlers(stack, isWindowError) {
|
130
|
+
var exception = null;
|
131
|
+
if (isWindowError && !TraceKit.collectWindowErrors) {
|
132
|
+
return;
|
133
|
+
}
|
134
|
+
for (var i in handlers) {
|
135
|
+
if (hasKey(handlers, i)) {
|
136
|
+
try {
|
137
|
+
handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
|
138
|
+
} catch (inner) {
|
139
|
+
exception = inner;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
if (exception) {
|
145
|
+
throw exception;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
var _oldOnerrorHandler, _onErrorHandlerInstalled;
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Ensures all global unhandled exceptions are recorded.
|
153
|
+
* Supported by Gecko and IE.
|
154
|
+
* @param {string} message Error message.
|
155
|
+
* @param {string} url URL of script that generated the exception.
|
156
|
+
* @param {(number|string)} lineNo The line number at which the error
|
157
|
+
* occurred.
|
158
|
+
* @param {?(number|string)} colNo The column number at which the error
|
159
|
+
* occurred.
|
160
|
+
* @param {?Error} ex The actual Error object.
|
161
|
+
*/
|
162
|
+
function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
|
163
|
+
var stack = null;
|
164
|
+
|
165
|
+
if (lastExceptionStack) {
|
166
|
+
TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
|
167
|
+
processLastException();
|
168
|
+
} else if (ex) {
|
169
|
+
// New chrome and blink send along a real error object
|
170
|
+
// Let's just report that like a normal error.
|
171
|
+
// See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
|
172
|
+
stack = TraceKit.computeStackTrace(ex);
|
173
|
+
notifyHandlers(stack, true);
|
174
|
+
} else {
|
175
|
+
var location = {
|
176
|
+
'url': url,
|
177
|
+
'line': lineNo,
|
178
|
+
'column': colNo
|
179
|
+
};
|
180
|
+
location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
|
181
|
+
location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
|
182
|
+
stack = {
|
183
|
+
'message': message,
|
184
|
+
'url': document.location.href,
|
185
|
+
'stack': [location]
|
186
|
+
};
|
187
|
+
notifyHandlers(stack, true);
|
188
|
+
}
|
189
|
+
|
190
|
+
if (_oldOnerrorHandler) {
|
191
|
+
return _oldOnerrorHandler.apply(this, arguments);
|
192
|
+
}
|
193
|
+
|
194
|
+
return false;
|
195
|
+
}
|
196
|
+
|
197
|
+
function installGlobalHandler ()
|
198
|
+
{
|
199
|
+
if (_onErrorHandlerInstalled) {
|
200
|
+
return;
|
201
|
+
}
|
202
|
+
_oldOnerrorHandler = window.onerror;
|
203
|
+
window.onerror = traceKitWindowOnError;
|
204
|
+
_onErrorHandlerInstalled = true;
|
205
|
+
}
|
206
|
+
|
207
|
+
function uninstallGlobalHandler ()
|
208
|
+
{
|
209
|
+
if (!_onErrorHandlerInstalled) {
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
window.onerror = _oldOnerrorHandler;
|
213
|
+
_onErrorHandlerInstalled = false;
|
214
|
+
_oldOnerrorHandler = undefined;
|
215
|
+
}
|
216
|
+
|
217
|
+
function processLastException() {
|
218
|
+
var _lastExceptionStack = lastExceptionStack,
|
219
|
+
_lastArgs = lastArgs;
|
220
|
+
lastArgs = null;
|
221
|
+
lastExceptionStack = null;
|
222
|
+
lastException = null;
|
223
|
+
notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
|
224
|
+
}
|
225
|
+
|
226
|
+
/**
|
227
|
+
* Reports an unhandled Error to TraceKit.
|
228
|
+
* @param {Error} ex
|
229
|
+
* @param {?boolean} rethrow If false, do not re-throw the exception.
|
230
|
+
* Only used for window.onerror to not cause an infinite loop of
|
231
|
+
* rethrowing.
|
232
|
+
*/
|
233
|
+
function report(ex, rethrow) {
|
234
|
+
var args = _slice.call(arguments, 1);
|
235
|
+
if (lastExceptionStack) {
|
236
|
+
if (lastException === ex) {
|
237
|
+
return; // already caught by an inner catch block, ignore
|
238
|
+
} else {
|
239
|
+
processLastException();
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
var stack = TraceKit.computeStackTrace(ex);
|
244
|
+
lastExceptionStack = stack;
|
245
|
+
lastException = ex;
|
246
|
+
lastArgs = args;
|
247
|
+
|
248
|
+
// If the stack trace is incomplete, wait for 2 seconds for
|
249
|
+
// slow slow IE to see if onerror occurs or not before reporting
|
250
|
+
// this exception; otherwise, we will end up with an incomplete
|
251
|
+
// stack trace
|
252
|
+
window.setTimeout(function () {
|
253
|
+
if (lastException === ex) {
|
254
|
+
processLastException();
|
255
|
+
}
|
256
|
+
}, (stack.incomplete ? 2000 : 0));
|
257
|
+
|
258
|
+
if (rethrow !== false) {
|
259
|
+
throw ex; // re-throw to propagate to the top level (and cause window.onerror)
|
260
|
+
}
|
261
|
+
}
|
262
|
+
|
263
|
+
report.subscribe = subscribe;
|
264
|
+
report.unsubscribe = unsubscribe;
|
265
|
+
report.uninstall = unsubscribeAll;
|
266
|
+
return report;
|
267
|
+
}());
|
268
|
+
|
269
|
+
/**
|
270
|
+
* TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
|
271
|
+
*
|
272
|
+
* Syntax:
|
273
|
+
* s = TraceKit.computeStackTrace.ofCaller([depth])
|
274
|
+
* s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
|
275
|
+
* Returns:
|
276
|
+
* s.name - exception name
|
277
|
+
* s.message - exception message
|
278
|
+
* s.stack[i].url - JavaScript or HTML file URL
|
279
|
+
* s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
|
280
|
+
* s.stack[i].args - arguments passed to the function, if known
|
281
|
+
* s.stack[i].line - line number, if known
|
282
|
+
* s.stack[i].column - column number, if known
|
283
|
+
* s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
|
284
|
+
*
|
285
|
+
* Supports:
|
286
|
+
* - Firefox: full stack trace with line numbers and unreliable column
|
287
|
+
* number on top frame
|
288
|
+
* - Opera 10: full stack trace with line and column numbers
|
289
|
+
* - Opera 9-: full stack trace with line numbers
|
290
|
+
* - Chrome: full stack trace with line and column numbers
|
291
|
+
* - Safari: line and column number for the topmost stacktrace element
|
292
|
+
* only
|
293
|
+
* - IE: no line numbers whatsoever
|
294
|
+
*
|
295
|
+
* Tries to guess names of anonymous functions by looking for assignments
|
296
|
+
* in the source code. In IE and Safari, we have to guess source file names
|
297
|
+
* by searching for function bodies inside all page scripts. This will not
|
298
|
+
* work for scripts that are loaded cross-domain.
|
299
|
+
* Here be dragons: some function names may be guessed incorrectly, and
|
300
|
+
* duplicate functions may be mismatched.
|
301
|
+
*
|
302
|
+
* TraceKit.computeStackTrace should only be used for tracing purposes.
|
303
|
+
* Logging of unhandled exceptions should be done with TraceKit.report,
|
304
|
+
* which builds on top of TraceKit.computeStackTrace and provides better
|
305
|
+
* IE support by utilizing the window.onerror event to retrieve information
|
306
|
+
* about the top of the stack.
|
307
|
+
*
|
308
|
+
* Note: In IE and Safari, no stack trace is recorded on the Error object,
|
309
|
+
* so computeStackTrace instead walks its *own* chain of callers.
|
310
|
+
* This means that:
|
311
|
+
* * in Safari, some methods may be missing from the stack trace;
|
312
|
+
* * in IE, the topmost function in the stack trace will always be the
|
313
|
+
* caller of computeStackTrace.
|
314
|
+
*
|
315
|
+
* This is okay for tracing (because you are likely to be calling
|
316
|
+
* computeStackTrace from the function you want to be the topmost element
|
317
|
+
* of the stack trace anyway), but not okay for logging unhandled
|
318
|
+
* exceptions (because your catch block will likely be far away from the
|
319
|
+
* inner function that actually caused the exception).
|
320
|
+
*
|
321
|
+
* Tracing example:
|
322
|
+
* function trace(message) {
|
323
|
+
* var stackInfo = TraceKit.computeStackTrace.ofCaller();
|
324
|
+
* var data = message + "\n";
|
325
|
+
* for(var i in stackInfo.stack) {
|
326
|
+
* var item = stackInfo.stack[i];
|
327
|
+
* data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
|
328
|
+
* }
|
329
|
+
* if (window.console)
|
330
|
+
* console.info(data);
|
331
|
+
* else
|
332
|
+
* alert(data);
|
333
|
+
* }
|
334
|
+
*/
|
335
|
+
TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
336
|
+
var debug = false,
|
337
|
+
sourceCache = {};
|
338
|
+
|
339
|
+
/**
|
340
|
+
* Attempts to retrieve source code via XMLHttpRequest, which is used
|
341
|
+
* to look up anonymous function names.
|
342
|
+
* @param {string} url URL of source code.
|
343
|
+
* @return {string} Source contents.
|
344
|
+
*/
|
345
|
+
function loadSource(url) {
|
346
|
+
if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
|
347
|
+
return '';
|
348
|
+
}
|
349
|
+
try {
|
350
|
+
var getXHR = function() {
|
351
|
+
try {
|
352
|
+
return new window.XMLHttpRequest();
|
353
|
+
} catch (e) {
|
354
|
+
// explicitly bubble up the exception if not found
|
355
|
+
return new window.ActiveXObject('Microsoft.XMLHTTP');
|
356
|
+
}
|
357
|
+
};
|
358
|
+
|
359
|
+
var request = getXHR();
|
360
|
+
request.open('GET', url, false);
|
361
|
+
request.send('');
|
362
|
+
return request.responseText;
|
363
|
+
} catch (e) {
|
364
|
+
return '';
|
365
|
+
}
|
366
|
+
}
|
367
|
+
|
368
|
+
/**
|
369
|
+
* Retrieves source code from the source code cache.
|
370
|
+
* @param {string} url URL of source code.
|
371
|
+
* @return {Array.<string>} Source contents.
|
372
|
+
*/
|
373
|
+
function getSource(url) {
|
374
|
+
if (!isString(url)) return [];
|
375
|
+
if (!hasKey(sourceCache, url)) {
|
376
|
+
// URL needs to be able to fetched within the acceptable domain. Otherwise,
|
377
|
+
// cross-domain errors will be triggered.
|
378
|
+
var source = '';
|
379
|
+
if (url.indexOf(document.domain) !== -1) {
|
380
|
+
source = loadSource(url);
|
381
|
+
}
|
382
|
+
sourceCache[url] = source ? source.split('\n') : [];
|
383
|
+
}
|
384
|
+
|
385
|
+
return sourceCache[url];
|
386
|
+
}
|
387
|
+
|
388
|
+
/**
|
389
|
+
* Tries to use an externally loaded copy of source code to determine
|
390
|
+
* the name of a function by looking at the name of the variable it was
|
391
|
+
* assigned to, if any.
|
392
|
+
* @param {string} url URL of source code.
|
393
|
+
* @param {(string|number)} lineNo Line number in source code.
|
394
|
+
* @return {string} The function name, if discoverable.
|
395
|
+
*/
|
396
|
+
function guessFunctionName(url, lineNo) {
|
397
|
+
var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
|
398
|
+
reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
|
399
|
+
line = '',
|
400
|
+
maxLines = 10,
|
401
|
+
source = getSource(url),
|
402
|
+
m;
|
403
|
+
|
404
|
+
if (!source.length) {
|
405
|
+
return UNKNOWN_FUNCTION;
|
406
|
+
}
|
407
|
+
|
408
|
+
// Walk backwards from the first line in the function until we find the line which
|
409
|
+
// matches the pattern above, which is the function definition
|
410
|
+
for (var i = 0; i < maxLines; ++i) {
|
411
|
+
line = source[lineNo - i] + line;
|
412
|
+
|
413
|
+
if (!isUndefined(line)) {
|
414
|
+
if ((m = reGuessFunction.exec(line))) {
|
415
|
+
return m[1];
|
416
|
+
} else if ((m = reFunctionArgNames.exec(line))) {
|
417
|
+
return m[1];
|
418
|
+
}
|
419
|
+
}
|
420
|
+
}
|
421
|
+
|
422
|
+
return UNKNOWN_FUNCTION;
|
423
|
+
}
|
424
|
+
|
425
|
+
/**
|
426
|
+
* Retrieves the surrounding lines from where an exception occurred.
|
427
|
+
* @param {string} url URL of source code.
|
428
|
+
* @param {(string|number)} line Line number in source code to centre
|
429
|
+
* around for context.
|
430
|
+
* @return {?Array.<string>} Lines of source code.
|
431
|
+
*/
|
432
|
+
function gatherContext(url, line) {
|
433
|
+
var source = getSource(url);
|
434
|
+
|
435
|
+
if (!source.length) {
|
436
|
+
return null;
|
437
|
+
}
|
438
|
+
|
439
|
+
var context = [],
|
440
|
+
// linesBefore & linesAfter are inclusive with the offending line.
|
441
|
+
// if linesOfContext is even, there will be one extra line
|
442
|
+
// *before* the offending line.
|
443
|
+
linesBefore = Math.floor(TraceKit.linesOfContext / 2),
|
444
|
+
// Add one extra line if linesOfContext is odd
|
445
|
+
linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
|
446
|
+
start = Math.max(0, line - linesBefore - 1),
|
447
|
+
end = Math.min(source.length, line + linesAfter - 1);
|
448
|
+
|
449
|
+
line -= 1; // convert to 0-based index
|
450
|
+
|
451
|
+
for (var i = start; i < end; ++i) {
|
452
|
+
if (!isUndefined(source[i])) {
|
453
|
+
context.push(source[i]);
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
457
|
+
return context.length > 0 ? context : null;
|
458
|
+
}
|
459
|
+
|
460
|
+
/**
|
461
|
+
* Escapes special characters, except for whitespace, in a string to be
|
462
|
+
* used inside a regular expression as a string literal.
|
463
|
+
* @param {string} text The string.
|
464
|
+
* @return {string} The escaped string literal.
|
465
|
+
*/
|
466
|
+
function escapeRegExp(text) {
|
467
|
+
return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
|
468
|
+
}
|
469
|
+
|
470
|
+
/**
|
471
|
+
* Escapes special characters in a string to be used inside a regular
|
472
|
+
* expression as a string literal. Also ensures that HTML entities will
|
473
|
+
* be matched the same as their literal friends.
|
474
|
+
* @param {string} body The string.
|
475
|
+
* @return {string} The escaped string.
|
476
|
+
*/
|
477
|
+
function escapeCodeAsRegExpForMatchingInsideHTML(body) {
|
478
|
+
return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+');
|
479
|
+
}
|
480
|
+
|
481
|
+
/**
|
482
|
+
* Determines where a code fragment occurs in the source code.
|
483
|
+
* @param {RegExp} re The function definition.
|
484
|
+
* @param {Array.<string>} urls A list of URLs to search.
|
485
|
+
* @return {?Object.<string, (string|number)>} An object containing
|
486
|
+
* the url, line, and column number of the defined function.
|
487
|
+
*/
|
488
|
+
function findSourceInUrls(re, urls) {
|
489
|
+
var source, m;
|
490
|
+
for (var i = 0, j = urls.length; i < j; ++i) {
|
491
|
+
// console.log('searching', urls[i]);
|
492
|
+
if ((source = getSource(urls[i])).length) {
|
493
|
+
source = source.join('\n');
|
494
|
+
if ((m = re.exec(source))) {
|
495
|
+
// console.log('Found function in ' + urls[i]);
|
496
|
+
|
497
|
+
return {
|
498
|
+
'url': urls[i],
|
499
|
+
'line': source.substring(0, m.index).split('\n').length,
|
500
|
+
'column': m.index - source.lastIndexOf('\n', m.index) - 1
|
501
|
+
};
|
502
|
+
}
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
// console.log('no match');
|
507
|
+
|
508
|
+
return null;
|
509
|
+
}
|
510
|
+
|
511
|
+
/**
|
512
|
+
* Determines at which column a code fragment occurs on a line of the
|
513
|
+
* source code.
|
514
|
+
* @param {string} fragment The code fragment.
|
515
|
+
* @param {string} url The URL to search.
|
516
|
+
* @param {(string|number)} line The line number to examine.
|
517
|
+
* @return {?number} The column number.
|
518
|
+
*/
|
519
|
+
function findSourceInLine(fragment, url, line) {
|
520
|
+
var source = getSource(url),
|
521
|
+
re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
|
522
|
+
m;
|
523
|
+
|
524
|
+
line -= 1;
|
525
|
+
|
526
|
+
if (source && source.length > line && (m = re.exec(source[line]))) {
|
527
|
+
return m.index;
|
528
|
+
}
|
529
|
+
|
530
|
+
return null;
|
531
|
+
}
|
532
|
+
|
533
|
+
/**
|
534
|
+
* Determines where a function was defined within the source code.
|
535
|
+
* @param {(Function|string)} func A function reference or serialized
|
536
|
+
* function definition.
|
537
|
+
* @return {?Object.<string, (string|number)>} An object containing
|
538
|
+
* the url, line, and column number of the defined function.
|
539
|
+
*/
|
540
|
+
function findSourceByFunctionBody(func) {
|
541
|
+
var urls = [window.location.href],
|
542
|
+
scripts = document.getElementsByTagName('script'),
|
543
|
+
body,
|
544
|
+
code = '' + func,
|
545
|
+
codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
|
546
|
+
eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
|
547
|
+
re,
|
548
|
+
parts,
|
549
|
+
result;
|
550
|
+
|
551
|
+
for (var i = 0; i < scripts.length; ++i) {
|
552
|
+
var script = scripts[i];
|
553
|
+
if (script.src) {
|
554
|
+
urls.push(script.src);
|
555
|
+
}
|
556
|
+
}
|
557
|
+
|
558
|
+
if (!(parts = codeRE.exec(code))) {
|
559
|
+
re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
|
560
|
+
}
|
561
|
+
|
562
|
+
// not sure if this is really necessary, but I don’t have a test
|
563
|
+
// corpus large enough to confirm that and it was in the original.
|
564
|
+
else {
|
565
|
+
var name = parts[1] ? '\\s+' + parts[1] : '',
|
566
|
+
args = parts[2].split(',').join('\\s*,\\s*');
|
567
|
+
|
568
|
+
body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
|
569
|
+
re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
|
570
|
+
}
|
571
|
+
|
572
|
+
// look for a normal function definition
|
573
|
+
if ((result = findSourceInUrls(re, urls))) {
|
574
|
+
return result;
|
575
|
+
}
|
576
|
+
|
577
|
+
// look for an old-school event handler function
|
578
|
+
if ((parts = eventRE.exec(code))) {
|
579
|
+
var event = parts[1];
|
580
|
+
body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
|
581
|
+
|
582
|
+
// look for a function defined in HTML as an onXXX handler
|
583
|
+
re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
|
584
|
+
|
585
|
+
if ((result = findSourceInUrls(re, urls[0]))) {
|
586
|
+
return result;
|
587
|
+
}
|
588
|
+
|
589
|
+
// look for ???
|
590
|
+
re = new RegExp(body);
|
591
|
+
|
592
|
+
if ((result = findSourceInUrls(re, urls))) {
|
593
|
+
return result;
|
594
|
+
}
|
595
|
+
}
|
596
|
+
|
597
|
+
return null;
|
598
|
+
}
|
599
|
+
|
600
|
+
// Contents of Exception in various browsers.
|
601
|
+
//
|
602
|
+
// SAFARI:
|
603
|
+
// ex.message = Can't find variable: qq
|
604
|
+
// ex.line = 59
|
605
|
+
// ex.sourceId = 580238192
|
606
|
+
// ex.sourceURL = http://...
|
607
|
+
// ex.expressionBeginOffset = 96
|
608
|
+
// ex.expressionCaretOffset = 98
|
609
|
+
// ex.expressionEndOffset = 98
|
610
|
+
// ex.name = ReferenceError
|
611
|
+
//
|
612
|
+
// FIREFOX:
|
613
|
+
// ex.message = qq is not defined
|
614
|
+
// ex.fileName = http://...
|
615
|
+
// ex.lineNumber = 59
|
616
|
+
// ex.columnNumber = 69
|
617
|
+
// ex.stack = ...stack trace... (see the example below)
|
618
|
+
// ex.name = ReferenceError
|
619
|
+
//
|
620
|
+
// CHROME:
|
621
|
+
// ex.message = qq is not defined
|
622
|
+
// ex.name = ReferenceError
|
623
|
+
// ex.type = not_defined
|
624
|
+
// ex.arguments = ['aa']
|
625
|
+
// ex.stack = ...stack trace...
|
626
|
+
//
|
627
|
+
// INTERNET EXPLORER:
|
628
|
+
// ex.message = ...
|
629
|
+
// ex.name = ReferenceError
|
630
|
+
//
|
631
|
+
// OPERA:
|
632
|
+
// ex.message = ...message... (see the example below)
|
633
|
+
// ex.name = ReferenceError
|
634
|
+
// ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
|
635
|
+
// ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
|
636
|
+
|
637
|
+
/**
|
638
|
+
* Computes stack trace information from the stack property.
|
639
|
+
* Chrome and Gecko use this property.
|
640
|
+
* @param {Error} ex
|
641
|
+
* @return {?Object.<string, *>} Stack trace information.
|
642
|
+
*/
|
643
|
+
function computeStackTraceFromStackProp(ex) {
|
644
|
+
if (!ex.stack) {
|
645
|
+
return null;
|
646
|
+
}
|
647
|
+
|
648
|
+
var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
|
649
|
+
gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i,
|
650
|
+
lines = ex.stack.split('\n'),
|
651
|
+
stack = [],
|
652
|
+
parts,
|
653
|
+
element,
|
654
|
+
reference = /^(.*) is undefined$/.exec(ex.message);
|
655
|
+
|
656
|
+
for (var i = 0, j = lines.length; i < j; ++i) {
|
657
|
+
if ((parts = gecko.exec(lines[i]))) {
|
658
|
+
element = {
|
659
|
+
'url': parts[3],
|
660
|
+
'func': parts[1] || UNKNOWN_FUNCTION,
|
661
|
+
'args': parts[2] ? parts[2].split(',') : '',
|
662
|
+
'line': +parts[4],
|
663
|
+
'column': parts[5] ? +parts[5] : null
|
664
|
+
};
|
665
|
+
} else if ((parts = chrome.exec(lines[i]))) {
|
666
|
+
element = {
|
667
|
+
'url': parts[2],
|
668
|
+
'func': parts[1] || UNKNOWN_FUNCTION,
|
669
|
+
'line': +parts[3],
|
670
|
+
'column': parts[4] ? +parts[4] : null
|
671
|
+
};
|
672
|
+
} else {
|
673
|
+
continue;
|
674
|
+
}
|
675
|
+
|
676
|
+
if (!element.func && element.line) {
|
677
|
+
element.func = guessFunctionName(element.url, element.line);
|
678
|
+
}
|
679
|
+
|
680
|
+
if (element.line) {
|
681
|
+
element.context = gatherContext(element.url, element.line);
|
682
|
+
}
|
683
|
+
|
684
|
+
stack.push(element);
|
685
|
+
}
|
686
|
+
|
687
|
+
if (!stack.length) {
|
688
|
+
return null;
|
689
|
+
}
|
690
|
+
|
691
|
+
if (stack[0].line && !stack[0].column && reference) {
|
692
|
+
stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
|
693
|
+
} else if (!stack[0].column && !isUndefined(ex.columnNumber)) {
|
694
|
+
// FireFox uses this awesome columnNumber property for its top frame
|
695
|
+
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
|
696
|
+
// so adding 1
|
697
|
+
stack[0].column = ex.columnNumber + 1;
|
698
|
+
}
|
699
|
+
|
700
|
+
return {
|
701
|
+
'name': ex.name,
|
702
|
+
'message': ex.message,
|
703
|
+
'url': document.location.href,
|
704
|
+
'stack': stack
|
705
|
+
};
|
706
|
+
}
|
707
|
+
|
708
|
+
/**
|
709
|
+
* Computes stack trace information from the stacktrace property.
|
710
|
+
* Opera 10 uses this property.
|
711
|
+
* @param {Error} ex
|
712
|
+
* @return {?Object.<string, *>} Stack trace information.
|
713
|
+
*/
|
714
|
+
function computeStackTraceFromStacktraceProp(ex) {
|
715
|
+
// Access and store the stacktrace property before doing ANYTHING
|
716
|
+
// else to it because Opera is not very good at providing it
|
717
|
+
// reliably in other circumstances.
|
718
|
+
var stacktrace = ex.stacktrace;
|
719
|
+
|
720
|
+
var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,
|
721
|
+
lines = stacktrace.split('\n'),
|
722
|
+
stack = [],
|
723
|
+
parts;
|
724
|
+
|
725
|
+
for (var i = 0, j = lines.length; i < j; i += 2) {
|
726
|
+
if ((parts = testRE.exec(lines[i]))) {
|
727
|
+
var element = {
|
728
|
+
'line': +parts[1],
|
729
|
+
'column': +parts[2],
|
730
|
+
'func': parts[3] || parts[4],
|
731
|
+
'args': parts[5] ? parts[5].split(',') : [],
|
732
|
+
'url': parts[6]
|
733
|
+
};
|
734
|
+
|
735
|
+
if (!element.func && element.line) {
|
736
|
+
element.func = guessFunctionName(element.url, element.line);
|
737
|
+
}
|
738
|
+
if (element.line) {
|
739
|
+
try {
|
740
|
+
element.context = gatherContext(element.url, element.line);
|
741
|
+
} catch (exc) {}
|
742
|
+
}
|
743
|
+
|
744
|
+
if (!element.context) {
|
745
|
+
element.context = [lines[i + 1]];
|
746
|
+
}
|
747
|
+
|
748
|
+
stack.push(element);
|
749
|
+
}
|
750
|
+
}
|
751
|
+
|
752
|
+
if (!stack.length) {
|
753
|
+
return null;
|
754
|
+
}
|
755
|
+
|
756
|
+
return {
|
757
|
+
'name': ex.name,
|
758
|
+
'message': ex.message,
|
759
|
+
'url': document.location.href,
|
760
|
+
'stack': stack
|
761
|
+
};
|
762
|
+
}
|
763
|
+
|
764
|
+
/**
|
765
|
+
* NOT TESTED.
|
766
|
+
* Computes stack trace information from an error message that includes
|
767
|
+
* the stack trace.
|
768
|
+
* Opera 9 and earlier use this method if the option to show stack
|
769
|
+
* traces is turned on in opera:config.
|
770
|
+
* @param {Error} ex
|
771
|
+
* @return {?Object.<string, *>} Stack information.
|
772
|
+
*/
|
773
|
+
function computeStackTraceFromOperaMultiLineMessage(ex) {
|
774
|
+
// Opera includes a stack trace into the exception message. An example is:
|
775
|
+
//
|
776
|
+
// Statement on line 3: Undefined variable: undefinedFunc
|
777
|
+
// Backtrace:
|
778
|
+
// Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
|
779
|
+
// undefinedFunc(a);
|
780
|
+
// Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
|
781
|
+
// zzz(x, y, z);
|
782
|
+
// Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
|
783
|
+
// yyy(a, a, a);
|
784
|
+
// Line 1 of function script
|
785
|
+
// try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
|
786
|
+
// ...
|
787
|
+
|
788
|
+
var lines = ex.message.split('\n');
|
789
|
+
if (lines.length < 4) {
|
790
|
+
return null;
|
791
|
+
}
|
792
|
+
|
793
|
+
var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,
|
794
|
+
lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,
|
795
|
+
lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
|
796
|
+
stack = [],
|
797
|
+
scripts = document.getElementsByTagName('script'),
|
798
|
+
inlineScriptBlocks = [],
|
799
|
+
parts,
|
800
|
+
i,
|
801
|
+
len,
|
802
|
+
source;
|
803
|
+
|
804
|
+
for (i in scripts) {
|
805
|
+
if (hasKey(scripts, i) && !scripts[i].src) {
|
806
|
+
inlineScriptBlocks.push(scripts[i]);
|
807
|
+
}
|
808
|
+
}
|
809
|
+
|
810
|
+
for (i = 2, len = lines.length; i < len; i += 2) {
|
811
|
+
var item = null;
|
812
|
+
if ((parts = lineRE1.exec(lines[i]))) {
|
813
|
+
item = {
|
814
|
+
'url': parts[2],
|
815
|
+
'func': parts[3],
|
816
|
+
'line': +parts[1]
|
817
|
+
};
|
818
|
+
} else if ((parts = lineRE2.exec(lines[i]))) {
|
819
|
+
item = {
|
820
|
+
'url': parts[3],
|
821
|
+
'func': parts[4]
|
822
|
+
};
|
823
|
+
var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
|
824
|
+
var script = inlineScriptBlocks[parts[2] - 1];
|
825
|
+
if (script) {
|
826
|
+
source = getSource(item.url);
|
827
|
+
if (source) {
|
828
|
+
source = source.join('\n');
|
829
|
+
var pos = source.indexOf(script.innerText);
|
830
|
+
if (pos >= 0) {
|
831
|
+
item.line = relativeLine + source.substring(0, pos).split('\n').length;
|
832
|
+
}
|
833
|
+
}
|
834
|
+
}
|
835
|
+
} else if ((parts = lineRE3.exec(lines[i]))) {
|
836
|
+
var url = window.location.href.replace(/#.*$/, ''),
|
837
|
+
line = parts[1];
|
838
|
+
var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1]));
|
839
|
+
source = findSourceInUrls(re, [url]);
|
840
|
+
item = {
|
841
|
+
'url': url,
|
842
|
+
'line': source ? source.line : line,
|
843
|
+
'func': ''
|
844
|
+
};
|
845
|
+
}
|
846
|
+
|
847
|
+
if (item) {
|
848
|
+
if (!item.func) {
|
849
|
+
item.func = guessFunctionName(item.url, item.line);
|
850
|
+
}
|
851
|
+
var context = gatherContext(item.url, item.line);
|
852
|
+
var midline = (context ? context[Math.floor(context.length / 2)] : null);
|
853
|
+
if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) {
|
854
|
+
item.context = context;
|
855
|
+
} else {
|
856
|
+
// if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
|
857
|
+
item.context = [lines[i + 1]];
|
858
|
+
}
|
859
|
+
stack.push(item);
|
860
|
+
}
|
861
|
+
}
|
862
|
+
if (!stack.length) {
|
863
|
+
return null; // could not parse multiline exception message as Opera stack trace
|
864
|
+
}
|
865
|
+
|
866
|
+
return {
|
867
|
+
'name': ex.name,
|
868
|
+
'message': lines[0],
|
869
|
+
'url': document.location.href,
|
870
|
+
'stack': stack
|
871
|
+
};
|
872
|
+
}
|
873
|
+
|
874
|
+
/**
|
875
|
+
* Adds information about the first frame to incomplete stack traces.
|
876
|
+
* Safari and IE require this to get complete data on the first frame.
|
877
|
+
* @param {Object.<string, *>} stackInfo Stack trace information from
|
878
|
+
* one of the compute* methods.
|
879
|
+
* @param {string} url The URL of the script that caused an error.
|
880
|
+
* @param {(number|string)} lineNo The line number of the script that
|
881
|
+
* caused an error.
|
882
|
+
* @param {string=} message The error generated by the browser, which
|
883
|
+
* hopefully contains the name of the object that caused the error.
|
884
|
+
* @return {boolean} Whether or not the stack information was
|
885
|
+
* augmented.
|
886
|
+
*/
|
887
|
+
function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
|
888
|
+
var initial = {
|
889
|
+
'url': url,
|
890
|
+
'line': lineNo
|
891
|
+
};
|
892
|
+
|
893
|
+
if (initial.url && initial.line) {
|
894
|
+
stackInfo.incomplete = false;
|
895
|
+
|
896
|
+
if (!initial.func) {
|
897
|
+
initial.func = guessFunctionName(initial.url, initial.line);
|
898
|
+
}
|
899
|
+
|
900
|
+
if (!initial.context) {
|
901
|
+
initial.context = gatherContext(initial.url, initial.line);
|
902
|
+
}
|
903
|
+
|
904
|
+
var reference = / '([^']+)' /.exec(message);
|
905
|
+
if (reference) {
|
906
|
+
initial.column = findSourceInLine(reference[1], initial.url, initial.line);
|
907
|
+
}
|
908
|
+
|
909
|
+
if (stackInfo.stack.length > 0) {
|
910
|
+
if (stackInfo.stack[0].url === initial.url) {
|
911
|
+
if (stackInfo.stack[0].line === initial.line) {
|
912
|
+
return false; // already in stack trace
|
913
|
+
} else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
|
914
|
+
stackInfo.stack[0].line = initial.line;
|
915
|
+
stackInfo.stack[0].context = initial.context;
|
916
|
+
return false;
|
917
|
+
}
|
918
|
+
}
|
919
|
+
}
|
920
|
+
|
921
|
+
stackInfo.stack.unshift(initial);
|
922
|
+
stackInfo.partial = true;
|
923
|
+
return true;
|
924
|
+
} else {
|
925
|
+
stackInfo.incomplete = true;
|
926
|
+
}
|
927
|
+
|
928
|
+
return false;
|
929
|
+
}
|
930
|
+
|
931
|
+
/**
|
932
|
+
* Computes stack trace information by walking the arguments.caller
|
933
|
+
* chain at the time the exception occurred. This will cause earlier
|
934
|
+
* frames to be missed but is the only way to get any stack trace in
|
935
|
+
* Safari and IE. The top frame is restored by
|
936
|
+
* {@link augmentStackTraceWithInitialElement}.
|
937
|
+
* @param {Error} ex
|
938
|
+
* @return {?Object.<string, *>} Stack trace information.
|
939
|
+
*/
|
940
|
+
function computeStackTraceByWalkingCallerChain(ex, depth) {
|
941
|
+
var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
|
942
|
+
stack = [],
|
943
|
+
funcs = {},
|
944
|
+
recursion = false,
|
945
|
+
parts,
|
946
|
+
item,
|
947
|
+
source;
|
948
|
+
|
949
|
+
for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
|
950
|
+
if (curr === computeStackTrace || curr === TraceKit.report) {
|
951
|
+
// console.log('skipping internal function');
|
952
|
+
continue;
|
953
|
+
}
|
954
|
+
|
955
|
+
item = {
|
956
|
+
'url': null,
|
957
|
+
'func': UNKNOWN_FUNCTION,
|
958
|
+
'line': null,
|
959
|
+
'column': null
|
960
|
+
};
|
961
|
+
|
962
|
+
if (curr.name) {
|
963
|
+
item.func = curr.name;
|
964
|
+
} else if ((parts = functionName.exec(curr.toString()))) {
|
965
|
+
item.func = parts[1];
|
966
|
+
}
|
967
|
+
|
968
|
+
if ((source = findSourceByFunctionBody(curr))) {
|
969
|
+
item.url = source.url;
|
970
|
+
item.line = source.line;
|
971
|
+
|
972
|
+
if (item.func === UNKNOWN_FUNCTION) {
|
973
|
+
item.func = guessFunctionName(item.url, item.line);
|
974
|
+
}
|
975
|
+
|
976
|
+
var reference = / '([^']+)' /.exec(ex.message || ex.description);
|
977
|
+
if (reference) {
|
978
|
+
item.column = findSourceInLine(reference[1], source.url, source.line);
|
979
|
+
}
|
980
|
+
}
|
981
|
+
|
982
|
+
if (funcs['' + curr]) {
|
983
|
+
recursion = true;
|
984
|
+
}else{
|
985
|
+
funcs['' + curr] = true;
|
986
|
+
}
|
987
|
+
|
988
|
+
stack.push(item);
|
989
|
+
}
|
990
|
+
|
991
|
+
if (depth) {
|
992
|
+
// console.log('depth is ' + depth);
|
993
|
+
// console.log('stack is ' + stack.length);
|
994
|
+
stack.splice(0, depth);
|
995
|
+
}
|
996
|
+
|
997
|
+
var result = {
|
998
|
+
'name': ex.name,
|
999
|
+
'message': ex.message,
|
1000
|
+
'url': document.location.href,
|
1001
|
+
'stack': stack
|
1002
|
+
};
|
1003
|
+
augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
|
1004
|
+
return result;
|
1005
|
+
}
|
1006
|
+
|
1007
|
+
/**
|
1008
|
+
* Computes a stack trace for an exception.
|
1009
|
+
* @param {Error} ex
|
1010
|
+
* @param {(string|number)=} depth
|
1011
|
+
*/
|
1012
|
+
function computeStackTrace(ex, depth) {
|
1013
|
+
var stack = null;
|
1014
|
+
depth = (depth == null ? 0 : +depth);
|
1015
|
+
|
1016
|
+
try {
|
1017
|
+
// This must be tried first because Opera 10 *destroys*
|
1018
|
+
// its stacktrace property if you try to access the stack
|
1019
|
+
// property first!!
|
1020
|
+
stack = computeStackTraceFromStacktraceProp(ex);
|
1021
|
+
if (stack) {
|
1022
|
+
return stack;
|
1023
|
+
}
|
1024
|
+
} catch (e) {
|
1025
|
+
if (debug) {
|
1026
|
+
throw e;
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
try {
|
1031
|
+
stack = computeStackTraceFromStackProp(ex);
|
1032
|
+
if (stack) {
|
1033
|
+
return stack;
|
1034
|
+
}
|
1035
|
+
} catch (e) {
|
1036
|
+
if (debug) {
|
1037
|
+
throw e;
|
1038
|
+
}
|
1039
|
+
}
|
1040
|
+
|
1041
|
+
try {
|
1042
|
+
stack = computeStackTraceFromOperaMultiLineMessage(ex);
|
1043
|
+
if (stack) {
|
1044
|
+
return stack;
|
1045
|
+
}
|
1046
|
+
} catch (e) {
|
1047
|
+
if (debug) {
|
1048
|
+
throw e;
|
1049
|
+
}
|
1050
|
+
}
|
1051
|
+
|
1052
|
+
try {
|
1053
|
+
stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
|
1054
|
+
if (stack) {
|
1055
|
+
return stack;
|
1056
|
+
}
|
1057
|
+
} catch (e) {
|
1058
|
+
if (debug) {
|
1059
|
+
throw e;
|
1060
|
+
}
|
1061
|
+
}
|
1062
|
+
|
1063
|
+
return {};
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
/**
|
1067
|
+
* Logs a stacktrace starting from the previous call and working down.
|
1068
|
+
* @param {(number|string)=} depth How many frames deep to trace.
|
1069
|
+
* @return {Object.<string, *>} Stack trace information.
|
1070
|
+
*/
|
1071
|
+
function computeStackTraceOfCaller(depth) {
|
1072
|
+
depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
|
1073
|
+
try {
|
1074
|
+
throw new Error();
|
1075
|
+
} catch (ex) {
|
1076
|
+
return computeStackTrace(ex, depth + 1);
|
1077
|
+
}
|
1078
|
+
}
|
1079
|
+
|
1080
|
+
computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
|
1081
|
+
computeStackTrace.guessFunctionName = guessFunctionName;
|
1082
|
+
computeStackTrace.gatherContext = gatherContext;
|
1083
|
+
computeStackTrace.ofCaller = computeStackTraceOfCaller;
|
1084
|
+
|
1085
|
+
return computeStackTrace;
|
1086
|
+
}());
|
1087
|
+
|
1088
|
+
'use strict';
|
1089
|
+
|
1090
|
+
// First, check for JSON support
|
1091
|
+
// If there is no JSON, we no-op the core features of Raven
|
1092
|
+
// since JSON is required to encode the payload
|
1093
|
+
var _Raven = window.Raven,
|
1094
|
+
hasJSON = !!(window.JSON && window.JSON.stringify),
|
1095
|
+
lastCapturedException,
|
1096
|
+
lastEventId,
|
1097
|
+
globalServer,
|
1098
|
+
globalUser,
|
1099
|
+
globalKey,
|
1100
|
+
globalProject,
|
1101
|
+
globalOptions = {
|
1102
|
+
logger: 'javascript',
|
1103
|
+
ignoreErrors: [],
|
1104
|
+
ignoreUrls: [],
|
1105
|
+
whitelistUrls: [],
|
1106
|
+
includePaths: [],
|
1107
|
+
collectWindowErrors: true,
|
1108
|
+
tags: {},
|
1109
|
+
extra: {}
|
1110
|
+
},
|
1111
|
+
authQueryString,
|
1112
|
+
isRavenInstalled = false;
|
1113
|
+
|
1114
|
+
/*
|
1115
|
+
* The core Raven singleton
|
1116
|
+
*
|
1117
|
+
* @this {Raven}
|
1118
|
+
*/
|
1119
|
+
var Raven = {
|
1120
|
+
VERSION: '1.1.16',
|
1121
|
+
|
1122
|
+
debug: true,
|
1123
|
+
|
1124
|
+
/*
|
1125
|
+
* Allow multiple versions of Raven to be installed.
|
1126
|
+
* Strip Raven from the global context and returns the instance.
|
1127
|
+
*
|
1128
|
+
* @return {Raven}
|
1129
|
+
*/
|
1130
|
+
noConflict: function() {
|
1131
|
+
window.Raven = _Raven;
|
1132
|
+
return Raven;
|
1133
|
+
},
|
1134
|
+
|
1135
|
+
/*
|
1136
|
+
* Configure Raven with a DSN and extra options
|
1137
|
+
*
|
1138
|
+
* @param {string} dsn The public Sentry DSN
|
1139
|
+
* @param {object} options Optional set of of global options [optional]
|
1140
|
+
* @return {Raven}
|
1141
|
+
*/
|
1142
|
+
config: function(dsn, options) {
|
1143
|
+
if (globalServer) {
|
1144
|
+
logDebug('error', 'Error: Raven has already been configured');
|
1145
|
+
return Raven;
|
1146
|
+
}
|
1147
|
+
if (!dsn) return Raven;
|
1148
|
+
|
1149
|
+
var uri = parseDSN(dsn),
|
1150
|
+
lastSlash = uri.path.lastIndexOf('/'),
|
1151
|
+
path = uri.path.substr(1, lastSlash);
|
1152
|
+
|
1153
|
+
// merge in options
|
1154
|
+
if (options) {
|
1155
|
+
each(options, function(key, value){
|
1156
|
+
globalOptions[key] = value;
|
1157
|
+
});
|
1158
|
+
}
|
1159
|
+
|
1160
|
+
// "Script error." is hard coded into browsers for errors that it can't read.
|
1161
|
+
// this is the result of a script being pulled in from an external domain and CORS.
|
1162
|
+
globalOptions.ignoreErrors.push('Script error.');
|
1163
|
+
globalOptions.ignoreErrors.push('Script error');
|
1164
|
+
|
1165
|
+
// Other variants of external script errors:
|
1166
|
+
globalOptions.ignoreErrors.push('Javascript error: Script error on line 0');
|
1167
|
+
globalOptions.ignoreErrors.push('Javascript error: Script error. on line 0');
|
1168
|
+
|
1169
|
+
// join regexp rules into one big rule
|
1170
|
+
globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);
|
1171
|
+
globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false;
|
1172
|
+
globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false;
|
1173
|
+
globalOptions.includePaths = joinRegExp(globalOptions.includePaths);
|
1174
|
+
|
1175
|
+
globalKey = uri.user;
|
1176
|
+
globalProject = uri.path.substr(lastSlash + 1);
|
1177
|
+
|
1178
|
+
// assemble the endpoint from the uri pieces
|
1179
|
+
globalServer = '//' + uri.host +
|
1180
|
+
(uri.port ? ':' + uri.port : '') +
|
1181
|
+
'/' + path + 'api/' + globalProject + '/store/';
|
1182
|
+
|
1183
|
+
if (uri.protocol) {
|
1184
|
+
globalServer = uri.protocol + ':' + globalServer;
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
if (globalOptions.fetchContext) {
|
1188
|
+
TraceKit.remoteFetching = true;
|
1189
|
+
}
|
1190
|
+
|
1191
|
+
if (globalOptions.linesOfContext) {
|
1192
|
+
TraceKit.linesOfContext = globalOptions.linesOfContext;
|
1193
|
+
}
|
1194
|
+
|
1195
|
+
TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;
|
1196
|
+
|
1197
|
+
setAuthQueryString();
|
1198
|
+
|
1199
|
+
// return for chaining
|
1200
|
+
return Raven;
|
1201
|
+
},
|
1202
|
+
|
1203
|
+
/*
|
1204
|
+
* Installs a global window.onerror error handler
|
1205
|
+
* to capture and report uncaught exceptions.
|
1206
|
+
* At this point, install() is required to be called due
|
1207
|
+
* to the way TraceKit is set up.
|
1208
|
+
*
|
1209
|
+
* @return {Raven}
|
1210
|
+
*/
|
1211
|
+
install: function() {
|
1212
|
+
if (isSetup() && !isRavenInstalled) {
|
1213
|
+
TraceKit.report.subscribe(handleStackInfo);
|
1214
|
+
isRavenInstalled = true;
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
return Raven;
|
1218
|
+
},
|
1219
|
+
|
1220
|
+
/*
|
1221
|
+
* Wrap code within a context so Raven can capture errors
|
1222
|
+
* reliably across domains that is executed immediately.
|
1223
|
+
*
|
1224
|
+
* @param {object} options A specific set of options for this context [optional]
|
1225
|
+
* @param {function} func The callback to be immediately executed within the context
|
1226
|
+
* @param {array} args An array of arguments to be called with the callback [optional]
|
1227
|
+
*/
|
1228
|
+
context: function(options, func, args) {
|
1229
|
+
if (isFunction(options)) {
|
1230
|
+
args = func || [];
|
1231
|
+
func = options;
|
1232
|
+
options = undefined;
|
1233
|
+
}
|
1234
|
+
|
1235
|
+
return Raven.wrap(options, func).apply(this, args);
|
1236
|
+
},
|
1237
|
+
|
1238
|
+
/*
|
1239
|
+
* Wrap code within a context and returns back a new function to be executed
|
1240
|
+
*
|
1241
|
+
* @param {object} options A specific set of options for this context [optional]
|
1242
|
+
* @param {function} func The function to be wrapped in a new context
|
1243
|
+
* @return {function} The newly wrapped functions with a context
|
1244
|
+
*/
|
1245
|
+
wrap: function(options, func) {
|
1246
|
+
// 1 argument has been passed, and it's not a function
|
1247
|
+
// so just return it
|
1248
|
+
if (isUndefined(func) && !isFunction(options)) {
|
1249
|
+
return options;
|
1250
|
+
}
|
1251
|
+
|
1252
|
+
// options is optional
|
1253
|
+
if (isFunction(options)) {
|
1254
|
+
func = options;
|
1255
|
+
options = undefined;
|
1256
|
+
}
|
1257
|
+
|
1258
|
+
// At this point, we've passed along 2 arguments, and the second one
|
1259
|
+
// is not a function either, so we'll just return the second argument.
|
1260
|
+
if (!isFunction(func)) {
|
1261
|
+
return func;
|
1262
|
+
}
|
1263
|
+
|
1264
|
+
// We don't wanna wrap it twice!
|
1265
|
+
if (func.__raven__) {
|
1266
|
+
return func;
|
1267
|
+
}
|
1268
|
+
|
1269
|
+
function wrapped() {
|
1270
|
+
var args = [], i = arguments.length,
|
1271
|
+
deep = !options || options && options.deep !== false;
|
1272
|
+
// Recursively wrap all of a function's arguments that are
|
1273
|
+
// functions themselves.
|
1274
|
+
|
1275
|
+
while(i--) args[i] = deep ? Raven.wrap(options, arguments[i]) : arguments[i];
|
1276
|
+
|
1277
|
+
try {
|
1278
|
+
/*jshint -W040*/
|
1279
|
+
return func.apply(this, args);
|
1280
|
+
} catch(e) {
|
1281
|
+
Raven.captureException(e, options);
|
1282
|
+
throw e;
|
1283
|
+
}
|
1284
|
+
}
|
1285
|
+
|
1286
|
+
// copy over properties of the old function
|
1287
|
+
for (var property in func) {
|
1288
|
+
if (hasKey(func, property)) {
|
1289
|
+
wrapped[property] = func[property];
|
1290
|
+
}
|
1291
|
+
}
|
1292
|
+
|
1293
|
+
// Signal that this function has been wrapped already
|
1294
|
+
// for both debugging and to prevent it to being wrapped twice
|
1295
|
+
wrapped.__raven__ = true;
|
1296
|
+
wrapped.__inner__ = func;
|
1297
|
+
|
1298
|
+
return wrapped;
|
1299
|
+
},
|
1300
|
+
|
1301
|
+
/*
|
1302
|
+
* Uninstalls the global error handler.
|
1303
|
+
*
|
1304
|
+
* @return {Raven}
|
1305
|
+
*/
|
1306
|
+
uninstall: function() {
|
1307
|
+
TraceKit.report.uninstall();
|
1308
|
+
isRavenInstalled = false;
|
1309
|
+
|
1310
|
+
return Raven;
|
1311
|
+
},
|
1312
|
+
|
1313
|
+
/*
|
1314
|
+
* Manually capture an exception and send it over to Sentry
|
1315
|
+
*
|
1316
|
+
* @param {error} ex An exception to be logged
|
1317
|
+
* @param {object} options A specific set of options for this error [optional]
|
1318
|
+
* @return {Raven}
|
1319
|
+
*/
|
1320
|
+
captureException: function(ex, options) {
|
1321
|
+
// If not an Error is passed through, recall as a message instead
|
1322
|
+
if (!(ex instanceof Error)) return Raven.captureMessage(ex, options);
|
1323
|
+
|
1324
|
+
// Store the raw exception object for potential debugging and introspection
|
1325
|
+
lastCapturedException = ex;
|
1326
|
+
|
1327
|
+
// TraceKit.report will re-raise any exception passed to it,
|
1328
|
+
// which means you have to wrap it in try/catch. Instead, we
|
1329
|
+
// can wrap it here and only re-raise if TraceKit.report
|
1330
|
+
// raises an exception different from the one we asked to
|
1331
|
+
// report on.
|
1332
|
+
try {
|
1333
|
+
TraceKit.report(ex, options);
|
1334
|
+
} catch(ex1) {
|
1335
|
+
if(ex !== ex1) {
|
1336
|
+
throw ex1;
|
1337
|
+
}
|
1338
|
+
}
|
1339
|
+
|
1340
|
+
return Raven;
|
1341
|
+
},
|
1342
|
+
|
1343
|
+
/*
|
1344
|
+
* Manually send a message to Sentry
|
1345
|
+
*
|
1346
|
+
* @param {string} msg A plain message to be captured in Sentry
|
1347
|
+
* @param {object} options A specific set of options for this message [optional]
|
1348
|
+
* @return {Raven}
|
1349
|
+
*/
|
1350
|
+
captureMessage: function(msg, options) {
|
1351
|
+
// Fire away!
|
1352
|
+
send(
|
1353
|
+
objectMerge({
|
1354
|
+
message: msg + '' // Make sure it's actually a string
|
1355
|
+
}, options)
|
1356
|
+
);
|
1357
|
+
|
1358
|
+
return Raven;
|
1359
|
+
},
|
1360
|
+
|
1361
|
+
/*
|
1362
|
+
* Set/clear a user to be sent along with the payload.
|
1363
|
+
*
|
1364
|
+
* @param {object} user An object representing user data [optional]
|
1365
|
+
* @return {Raven}
|
1366
|
+
*/
|
1367
|
+
setUserContext: function(user) {
|
1368
|
+
globalUser = user;
|
1369
|
+
|
1370
|
+
return Raven;
|
1371
|
+
},
|
1372
|
+
|
1373
|
+
/*
|
1374
|
+
* Set extra attributes to be sent along with the payload.
|
1375
|
+
*
|
1376
|
+
* @param {object} extra An object representing extra data [optional]
|
1377
|
+
* @return {Raven}
|
1378
|
+
*/
|
1379
|
+
setExtraContext: function(extra) {
|
1380
|
+
globalOptions.extra = extra || {};
|
1381
|
+
|
1382
|
+
return Raven;
|
1383
|
+
},
|
1384
|
+
|
1385
|
+
/*
|
1386
|
+
* Set tags to be sent along with the payload.
|
1387
|
+
*
|
1388
|
+
* @param {object} tags An object representing tags [optional]
|
1389
|
+
* @return {Raven}
|
1390
|
+
*/
|
1391
|
+
setTagsContext: function(tags) {
|
1392
|
+
globalOptions.tags = tags || {};
|
1393
|
+
|
1394
|
+
return Raven;
|
1395
|
+
},
|
1396
|
+
|
1397
|
+
/*
|
1398
|
+
* Get the latest raw exception that was captured by Raven.
|
1399
|
+
*
|
1400
|
+
* @return {error}
|
1401
|
+
*/
|
1402
|
+
lastException: function() {
|
1403
|
+
return lastCapturedException;
|
1404
|
+
},
|
1405
|
+
|
1406
|
+
/*
|
1407
|
+
* Get the last event id
|
1408
|
+
*
|
1409
|
+
* @return {string}
|
1410
|
+
*/
|
1411
|
+
lastEventId: function() {
|
1412
|
+
return lastEventId;
|
1413
|
+
}
|
1414
|
+
};
|
1415
|
+
|
1416
|
+
Raven.setUser = Raven.setUserContext; // To be deprecated
|
1417
|
+
|
1418
|
+
function triggerEvent(eventType, options) {
|
1419
|
+
var event, key;
|
1420
|
+
|
1421
|
+
options = options || {};
|
1422
|
+
|
1423
|
+
eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1);
|
1424
|
+
|
1425
|
+
if (document.createEvent) {
|
1426
|
+
event = document.createEvent('HTMLEvents');
|
1427
|
+
event.initEvent(eventType, true, true);
|
1428
|
+
} else {
|
1429
|
+
event = document.createEventObject();
|
1430
|
+
event.eventType = eventType;
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
for (key in options) if (hasKey(options, key)) {
|
1434
|
+
event[key] = options[key];
|
1435
|
+
}
|
1436
|
+
|
1437
|
+
if (document.createEvent) {
|
1438
|
+
// IE9 if standards
|
1439
|
+
document.dispatchEvent(event);
|
1440
|
+
} else {
|
1441
|
+
// IE8 regardless of Quirks or Standards
|
1442
|
+
// IE9 if quirks
|
1443
|
+
try {
|
1444
|
+
document.fireEvent('on' + event.eventType.toLowerCase(), event);
|
1445
|
+
} catch(e) {}
|
1446
|
+
}
|
1447
|
+
}
|
1448
|
+
|
1449
|
+
var dsnKeys = 'source protocol user pass host port path'.split(' '),
|
1450
|
+
dsnPattern = /^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/;
|
1451
|
+
|
1452
|
+
function RavenConfigError(message) {
|
1453
|
+
this.name = 'RavenConfigError';
|
1454
|
+
this.message = message;
|
1455
|
+
}
|
1456
|
+
RavenConfigError.prototype = new Error();
|
1457
|
+
RavenConfigError.prototype.constructor = RavenConfigError;
|
1458
|
+
|
1459
|
+
/**** Private functions ****/
|
1460
|
+
function parseDSN(str) {
|
1461
|
+
var m = dsnPattern.exec(str),
|
1462
|
+
dsn = {},
|
1463
|
+
i = 7;
|
1464
|
+
|
1465
|
+
try {
|
1466
|
+
while (i--) dsn[dsnKeys[i]] = m[i] || '';
|
1467
|
+
} catch(e) {
|
1468
|
+
throw new RavenConfigError('Invalid DSN: ' + str);
|
1469
|
+
}
|
1470
|
+
|
1471
|
+
if (dsn.pass)
|
1472
|
+
throw new RavenConfigError('Do not specify your private key in the DSN!');
|
1473
|
+
|
1474
|
+
return dsn;
|
1475
|
+
}
|
1476
|
+
|
1477
|
+
function isUndefined(what) {
|
1478
|
+
return typeof what === 'undefined';
|
1479
|
+
}
|
1480
|
+
|
1481
|
+
function isFunction(what) {
|
1482
|
+
return typeof what === 'function';
|
1483
|
+
}
|
1484
|
+
|
1485
|
+
function isString(what) {
|
1486
|
+
return typeof what === 'string';
|
1487
|
+
}
|
1488
|
+
|
1489
|
+
function isEmptyObject(what) {
|
1490
|
+
for (var k in what) return false;
|
1491
|
+
return true;
|
1492
|
+
}
|
1493
|
+
|
1494
|
+
/**
|
1495
|
+
* hasKey, a better form of hasOwnProperty
|
1496
|
+
* Example: hasKey(MainHostObject, property) === true/false
|
1497
|
+
*
|
1498
|
+
* @param {Object} host object to check property
|
1499
|
+
* @param {string} key to check
|
1500
|
+
*/
|
1501
|
+
function hasKey(object, key) {
|
1502
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
1503
|
+
}
|
1504
|
+
|
1505
|
+
function each(obj, callback) {
|
1506
|
+
var i, j;
|
1507
|
+
|
1508
|
+
if (isUndefined(obj.length)) {
|
1509
|
+
for (i in obj) {
|
1510
|
+
if (hasKey(obj, i)) {
|
1511
|
+
callback.call(null, i, obj[i]);
|
1512
|
+
}
|
1513
|
+
}
|
1514
|
+
} else {
|
1515
|
+
j = obj.length;
|
1516
|
+
if (j) {
|
1517
|
+
for (i = 0; i < j; i++) {
|
1518
|
+
callback.call(null, i, obj[i]);
|
1519
|
+
}
|
1520
|
+
}
|
1521
|
+
}
|
1522
|
+
}
|
1523
|
+
|
1524
|
+
|
1525
|
+
function setAuthQueryString() {
|
1526
|
+
authQueryString =
|
1527
|
+
'?sentry_version=4' +
|
1528
|
+
'&sentry_client=raven-js/' + Raven.VERSION +
|
1529
|
+
'&sentry_key=' + globalKey;
|
1530
|
+
}
|
1531
|
+
|
1532
|
+
|
1533
|
+
function handleStackInfo(stackInfo, options) {
|
1534
|
+
var frames = [];
|
1535
|
+
|
1536
|
+
if (stackInfo.stack && stackInfo.stack.length) {
|
1537
|
+
each(stackInfo.stack, function(i, stack) {
|
1538
|
+
var frame = normalizeFrame(stack);
|
1539
|
+
if (frame) {
|
1540
|
+
frames.push(frame);
|
1541
|
+
}
|
1542
|
+
});
|
1543
|
+
}
|
1544
|
+
|
1545
|
+
triggerEvent('handle', {
|
1546
|
+
stackInfo: stackInfo,
|
1547
|
+
options: options
|
1548
|
+
});
|
1549
|
+
|
1550
|
+
processException(
|
1551
|
+
stackInfo.name,
|
1552
|
+
stackInfo.message,
|
1553
|
+
stackInfo.url,
|
1554
|
+
stackInfo.lineno,
|
1555
|
+
frames,
|
1556
|
+
options
|
1557
|
+
);
|
1558
|
+
}
|
1559
|
+
|
1560
|
+
function normalizeFrame(frame) {
|
1561
|
+
if (!frame.url) return;
|
1562
|
+
|
1563
|
+
// normalize the frames data
|
1564
|
+
var normalized = {
|
1565
|
+
filename: frame.url,
|
1566
|
+
lineno: frame.line,
|
1567
|
+
colno: frame.column,
|
1568
|
+
'function': frame.func || '?'
|
1569
|
+
}, context = extractContextFromFrame(frame), i;
|
1570
|
+
|
1571
|
+
if (context) {
|
1572
|
+
var keys = ['pre_context', 'context_line', 'post_context'];
|
1573
|
+
i = 3;
|
1574
|
+
while (i--) normalized[keys[i]] = context[i];
|
1575
|
+
}
|
1576
|
+
|
1577
|
+
normalized.in_app = !( // determine if an exception came from outside of our app
|
1578
|
+
// first we check the global includePaths list.
|
1579
|
+
!globalOptions.includePaths.test(normalized.filename) ||
|
1580
|
+
// Now we check for fun, if the function name is Raven or TraceKit
|
1581
|
+
/(Raven|TraceKit)\./.test(normalized['function']) ||
|
1582
|
+
// finally, we do a last ditch effort and check for raven.min.js
|
1583
|
+
/raven\.(min\.)?js$/.test(normalized.filename)
|
1584
|
+
);
|
1585
|
+
|
1586
|
+
return normalized;
|
1587
|
+
}
|
1588
|
+
|
1589
|
+
function extractContextFromFrame(frame) {
|
1590
|
+
// immediately check if we should even attempt to parse a context
|
1591
|
+
if (!frame.context || !globalOptions.fetchContext) return;
|
1592
|
+
|
1593
|
+
var context = frame.context,
|
1594
|
+
pivot = ~~(context.length / 2),
|
1595
|
+
i = context.length, isMinified = false;
|
1596
|
+
|
1597
|
+
while (i--) {
|
1598
|
+
// We're making a guess to see if the source is minified or not.
|
1599
|
+
// To do that, we make the assumption if *any* of the lines passed
|
1600
|
+
// in are greater than 300 characters long, we bail.
|
1601
|
+
// Sentry will see that there isn't a context
|
1602
|
+
if (context[i].length > 300) {
|
1603
|
+
isMinified = true;
|
1604
|
+
break;
|
1605
|
+
}
|
1606
|
+
}
|
1607
|
+
|
1608
|
+
if (isMinified) {
|
1609
|
+
// The source is minified and we don't know which column. Fuck it.
|
1610
|
+
if (isUndefined(frame.column)) return;
|
1611
|
+
|
1612
|
+
// If the source is minified and has a frame column
|
1613
|
+
// we take a chunk of the offending line to hopefully shed some light
|
1614
|
+
return [
|
1615
|
+
[], // no pre_context
|
1616
|
+
context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column
|
1617
|
+
[] // no post_context
|
1618
|
+
];
|
1619
|
+
}
|
1620
|
+
|
1621
|
+
return [
|
1622
|
+
context.slice(0, pivot), // pre_context
|
1623
|
+
context[pivot], // context_line
|
1624
|
+
context.slice(pivot + 1) // post_context
|
1625
|
+
];
|
1626
|
+
}
|
1627
|
+
|
1628
|
+
function processException(type, message, fileurl, lineno, frames, options) {
|
1629
|
+
var stacktrace, label, i;
|
1630
|
+
|
1631
|
+
// In some instances message is not actually a string, no idea why,
|
1632
|
+
// so we want to always coerce it to one.
|
1633
|
+
message += '';
|
1634
|
+
|
1635
|
+
// Sometimes an exception is getting logged in Sentry as
|
1636
|
+
// <no message value>
|
1637
|
+
// This can only mean that the message was falsey since this value
|
1638
|
+
// is hardcoded into Sentry itself.
|
1639
|
+
// At this point, if the message is falsey, we bail since it's useless
|
1640
|
+
if (type === 'Error' && !message) return;
|
1641
|
+
|
1642
|
+
if (globalOptions.ignoreErrors.test(message)) return;
|
1643
|
+
|
1644
|
+
if (frames && frames.length) {
|
1645
|
+
fileurl = frames[0].filename || fileurl;
|
1646
|
+
// Sentry expects frames oldest to newest
|
1647
|
+
// and JS sends them as newest to oldest
|
1648
|
+
frames.reverse();
|
1649
|
+
stacktrace = {frames: frames};
|
1650
|
+
} else if (fileurl) {
|
1651
|
+
stacktrace = {
|
1652
|
+
frames: [{
|
1653
|
+
filename: fileurl,
|
1654
|
+
lineno: lineno,
|
1655
|
+
in_app: true
|
1656
|
+
}]
|
1657
|
+
};
|
1658
|
+
}
|
1659
|
+
|
1660
|
+
// Truncate the message to a max of characters
|
1661
|
+
message = truncate(message, 100);
|
1662
|
+
|
1663
|
+
if (globalOptions.ignoreUrls && globalOptions.ignoreUrls.test(fileurl)) return;
|
1664
|
+
if (globalOptions.whitelistUrls && !globalOptions.whitelistUrls.test(fileurl)) return;
|
1665
|
+
|
1666
|
+
label = lineno ? message + ' at ' + lineno : message;
|
1667
|
+
|
1668
|
+
// Fire away!
|
1669
|
+
send(
|
1670
|
+
objectMerge({
|
1671
|
+
// sentry.interfaces.Exception
|
1672
|
+
exception: {
|
1673
|
+
type: type,
|
1674
|
+
value: message
|
1675
|
+
},
|
1676
|
+
// sentry.interfaces.Stacktrace
|
1677
|
+
stacktrace: stacktrace,
|
1678
|
+
culprit: fileurl,
|
1679
|
+
message: label
|
1680
|
+
}, options)
|
1681
|
+
);
|
1682
|
+
}
|
1683
|
+
|
1684
|
+
function objectMerge(obj1, obj2) {
|
1685
|
+
if (!obj2) {
|
1686
|
+
return obj1;
|
1687
|
+
}
|
1688
|
+
each(obj2, function(key, value){
|
1689
|
+
obj1[key] = value;
|
1690
|
+
});
|
1691
|
+
return obj1;
|
1692
|
+
}
|
1693
|
+
|
1694
|
+
function truncate(str, max) {
|
1695
|
+
return str.length <= max ? str : str.substr(0, max) + '\u2026';
|
1696
|
+
}
|
1697
|
+
|
1698
|
+
function getHttpData() {
|
1699
|
+
var http = {
|
1700
|
+
url: document.location.href,
|
1701
|
+
headers: {
|
1702
|
+
'User-Agent': navigator.userAgent
|
1703
|
+
}
|
1704
|
+
};
|
1705
|
+
|
1706
|
+
if (document.referrer) {
|
1707
|
+
http.headers.Referer = document.referrer;
|
1708
|
+
}
|
1709
|
+
|
1710
|
+
return http;
|
1711
|
+
}
|
1712
|
+
|
1713
|
+
function send(data) {
|
1714
|
+
if (!isSetup()) return;
|
1715
|
+
|
1716
|
+
data = objectMerge({
|
1717
|
+
project: globalProject,
|
1718
|
+
logger: globalOptions.logger,
|
1719
|
+
site: globalOptions.site,
|
1720
|
+
platform: 'javascript',
|
1721
|
+
// sentry.interfaces.Http
|
1722
|
+
request: getHttpData()
|
1723
|
+
}, data);
|
1724
|
+
|
1725
|
+
// Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
|
1726
|
+
data.tags = objectMerge(globalOptions.tags, data.tags);
|
1727
|
+
data.extra = objectMerge(globalOptions.extra, data.extra);
|
1728
|
+
|
1729
|
+
// If there are no tags/extra, strip the key from the payload alltogther.
|
1730
|
+
if (isEmptyObject(data.tags)) delete data.tags;
|
1731
|
+
if (isEmptyObject(data.extra)) delete data.extra;
|
1732
|
+
|
1733
|
+
if (globalUser) {
|
1734
|
+
// sentry.interfaces.User
|
1735
|
+
data.user = globalUser;
|
1736
|
+
}
|
1737
|
+
|
1738
|
+
if (isFunction(globalOptions.dataCallback)) {
|
1739
|
+
data = globalOptions.dataCallback(data);
|
1740
|
+
}
|
1741
|
+
|
1742
|
+
// Check if the request should be filtered or not
|
1743
|
+
if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) {
|
1744
|
+
return;
|
1745
|
+
}
|
1746
|
+
|
1747
|
+
// Send along an event_id if not explicitly passed.
|
1748
|
+
// This event_id can be used to reference the error within Sentry itself.
|
1749
|
+
// Set lastEventId after we know the error should actually be sent
|
1750
|
+
lastEventId = data.event_id || (data.event_id = uuid4());
|
1751
|
+
|
1752
|
+
makeRequest(data);
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
|
1756
|
+
function makeRequest(data) {
|
1757
|
+
var img = new Image(),
|
1758
|
+
src = globalServer + authQueryString + '&sentry_data=' + encodeURIComponent(JSON.stringify(data));
|
1759
|
+
|
1760
|
+
img.onload = function success() {
|
1761
|
+
triggerEvent('success', {
|
1762
|
+
data: data,
|
1763
|
+
src: src
|
1764
|
+
});
|
1765
|
+
};
|
1766
|
+
img.onerror = img.onabort = function failure() {
|
1767
|
+
triggerEvent('failure', {
|
1768
|
+
data: data,
|
1769
|
+
src: src
|
1770
|
+
});
|
1771
|
+
};
|
1772
|
+
img.src = src;
|
1773
|
+
}
|
1774
|
+
|
1775
|
+
function isSetup() {
|
1776
|
+
if (!hasJSON) return false; // needs JSON support
|
1777
|
+
if (!globalServer) {
|
1778
|
+
logDebug('error', 'Error: Raven has not been configured.');
|
1779
|
+
return false;
|
1780
|
+
}
|
1781
|
+
return true;
|
1782
|
+
}
|
1783
|
+
|
1784
|
+
function joinRegExp(patterns) {
|
1785
|
+
// Combine an array of regular expressions and strings into one large regexp
|
1786
|
+
// Be mad.
|
1787
|
+
var sources = [],
|
1788
|
+
i = 0, len = patterns.length,
|
1789
|
+
pattern;
|
1790
|
+
|
1791
|
+
for (; i < len; i++) {
|
1792
|
+
pattern = patterns[i];
|
1793
|
+
if (isString(pattern)) {
|
1794
|
+
// If it's a string, we need to escape it
|
1795
|
+
// Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
1796
|
+
sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"));
|
1797
|
+
} else if (pattern && pattern.source) {
|
1798
|
+
// If it's a regexp already, we want to extract the source
|
1799
|
+
sources.push(pattern.source);
|
1800
|
+
}
|
1801
|
+
// Intentionally skip other cases
|
1802
|
+
}
|
1803
|
+
return new RegExp(sources.join('|'), 'i');
|
1804
|
+
}
|
1805
|
+
|
1806
|
+
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
|
1807
|
+
function uuid4() {
|
1808
|
+
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
1809
|
+
var r = Math.random()*16|0,
|
1810
|
+
v = c == 'x' ? r : (r&0x3|0x8);
|
1811
|
+
return v.toString(16);
|
1812
|
+
});
|
1813
|
+
}
|
1814
|
+
|
1815
|
+
function logDebug(level, message) {
|
1816
|
+
if (window.console && console[level] && Raven.debug) {
|
1817
|
+
console[level](message);
|
1818
|
+
}
|
1819
|
+
}
|
1820
|
+
|
1821
|
+
function afterLoad() {
|
1822
|
+
// Attempt to initialize Raven on load
|
1823
|
+
var RavenConfig = window.RavenConfig;
|
1824
|
+
if (RavenConfig) {
|
1825
|
+
Raven.config(RavenConfig.dsn, RavenConfig.config).install();
|
1826
|
+
}
|
1827
|
+
}
|
1828
|
+
afterLoad();
|
1829
|
+
|
1830
|
+
// Expose Raven to the world
|
1831
|
+
window.Raven = Raven;
|
1832
|
+
|
1833
|
+
// AMD
|
1834
|
+
if (typeof define === 'function' && define.amd) {
|
1835
|
+
define('raven', [], function() { return Raven; });
|
1836
|
+
}
|
1837
|
+
|
1838
|
+
})(this);
|