History

Update Raven.js

Thibaut Courouble committed 10 years ago

Commit d6ffd861 parent 7d433c11 file · +2092 −1712Browse files

Unified · Split

assets/javascripts/vendor/raven.js+2092−1712
@@ -1,2055 +1,2435 @@
1/*! Raven.js 1.3.0 (768fdca) | github.com/getsentry/raven-js */1/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */
2 2
3/*3/*
4 * Includes TraceKit4 * Includes TraceKit
5 * https://github.com/getsentry/TraceKit5 * https://github.com/getsentry/TraceKit
6 *6 *
7 * Copyright 2015 Matt Robenolt and other contributors7 * Copyright 2016 Matt Robenolt and other contributors
8 * Released under the BSD license8 * Released under the BSD license
9 * https://github.com/getsentry/raven-js/blob/master/LICENSE9 * https://github.com/getsentry/raven-js/blob/master/LICENSE
10 *10 *
11 */11 */
12;(function(window, undefined){12
13(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
13'use strict';14'use strict';
14 15
15/*16function RavenConfigError(message) {
16 TraceKit - Cross brower stack traces - github.com/occ/TraceKit17 this.name = 'RavenConfigError';
17 MIT license18 this.message = message;
18*/19}
20RavenConfigError.prototype = new Error();
21RavenConfigError.prototype.constructor = RavenConfigError;
19 22
20var TraceKit = {23module.exports = RavenConfigError;
21 remoteFetching: false,
22 collectWindowErrors: true,
23 // 3 lines before, the offending line, 3 lines after
24 linesOfContext: 7,
25 debug: false
26};
27 24
28// global reference to slice25},{}],2:[function(_dereq_,module,exports){
29var _slice = [].slice;26/*global XDomainRequest:false*/
30var UNKNOWN_FUNCTION = '?';27'use strict';
31 28
29var TraceKit = _dereq_(5);
30var RavenConfigError = _dereq_(1);
31var utils = _dereq_(4);
32
33var isFunction = utils.isFunction;
34var isUndefined = utils.isUndefined;
35var isError = utils.isError;
36var isEmptyObject = utils.isEmptyObject;
37var hasKey = utils.hasKey;
38var joinRegExp = utils.joinRegExp;
39var each = utils.each;
40var objectMerge = utils.objectMerge;
41var truncate = utils.truncate;
42var urlencode = utils.urlencode;
43var uuid4 = utils.uuid4;
32 44
33function getLocationHref() {45var dsnKeys = 'source protocol user pass host port path'.split(' '),
34 if (typeof document === 'undefined')46 dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
35 return '';
36 47
37 return document.location.href;48function now() {
38};49 return +new Date();
50}
39 51
40/**52// First, check for JSON support
41 * TraceKit.report: cross-browser processing of unhandled exceptions53// If there is no JSON, we no-op the core features of Raven
42 *54// since JSON is required to encode the payload
43 * Syntax:55function Raven() {
44 * TraceKit.report.subscribe(function(stackInfo) { ... })56 this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);
45 * TraceKit.report.unsubscribe(function(stackInfo) { ... })57 // Raven can run in contexts where there's no document (react-native)
46 * TraceKit.report(exception)58 this._hasDocument = typeof document !== 'undefined';
47 * try { ...code... } catch(ex) { TraceKit.report(ex); }59 this._lastCapturedException = null;
48 *60 this._lastEventId = null;
49 * Supports:61 this._globalServer = null;
50 * - Firefox: full stack trace with line numbers, plus column number62 this._globalKey = null;
51 * on top frame; column number is not guaranteed63 this._globalProject = null;
52 * - Opera: full stack trace with line and column numbers64 this._globalContext = {};
53 * - Chrome: full stack trace with line and column numbers65 this._globalOptions = {
54 * - Safari: line and column number for the top frame only; some frames66 logger: 'javascript',
55 * may be missing, and column number is not guaranteed67 ignoreErrors: [],
56 * - IE: line and column number for the top frame only; some frames68 ignoreUrls: [],
57 * may be missing, and column number is not guaranteed69 whitelistUrls: [],
58 *70 includePaths: [],
59 * In theory, TraceKit should work on all of the following versions:71 crossOrigin: 'anonymous',
60 * - IE5.5+ (only 8.0 tested)72 collectWindowErrors: true,
61 * - Firefox 0.9+ (only 3.5+ tested)73 maxMessageLength: 0,
62 * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require74 stackTraceLimit: 50
63 * Exceptions Have Stacktrace to be enabled in opera:config)75 };
64 * - Safari 3+ (only 4+ tested)76 this._ignoreOnError = 0;
65 * - Chrome 1+ (only 5+ tested)77 this._isRavenInstalled = false;
66 * - Konqueror 3.5+ (untested)78 this._originalErrorStackTraceLimit = Error.stackTraceLimit;
67 *79 // capture references to window.console *and* all its methods first
68 * Requires TraceKit.computeStackTrace.80 // before the console plugin has a chance to monkey patch
69 *81 this._originalConsole = window.console || {};
70 * Tries to catch all unhandled exceptions and report them to the82 this._originalConsoleMethods = {};
71 * subscribed handlers. Please note that TraceKit.report will rethrow the83 this._plugins = [];
72 * exception. This is REQUIRED in order to get a useful stack trace in IE.84 this._startTime = now();
73 * If the exception does not reach the top of the browser, you will only85 this._wrappedBuiltIns = [];
74 * get a stack trace from the point where TraceKit.report was called.86
87 for (var method in this._originalConsole) { // eslint-disable-line guard-for-in
88 this._originalConsoleMethods[method] = this._originalConsole[method];
89 }
90}
91
92/*
93 * The core Raven singleton
75 *94 *
76 * Handlers receive a stackInfo object as described in the95 * @this {Raven}
77 * TraceKit.computeStackTrace docs.
78 */96 */
79TraceKit.report = (function reportModuleWrapper() {
80 var handlers = [],
81 lastArgs = null,
82 lastException = null,
83 lastExceptionStack = null;
84 97
85 /**98Raven.prototype = {
86 * Add a crash handler.99 // Hardcode version string so that raven source can be loaded directly via
87 * @param {Function} handler100 // webpack (using a build step causes webpack #1617). Grunt verifies that
88 */101 // this value matches package.json during build.
89 function subscribe(handler) {102 // See: https://github.com/getsentry/raven-js/issues/465
90 installGlobalHandler();103 VERSION: '2.3.0',
91 handlers.push(handler);
92 }
93 104
94 /**105 debug: false,
95 * Remove a crash handler.
96 * @param {Function} handler
97 */
98 function unsubscribe(handler) {
99 for (var i = handlers.length - 1; i >= 0; --i) {
100 if (handlers[i] === handler) {
101 handlers.splice(i, 1);
102 }
103 }
104 }
105 106
106 /**107 TraceKit: TraceKit, // alias to TraceKit
107 * Remove all crash handlers.
108 */
109 function unsubscribeAll() {
110 uninstallGlobalHandler();
111 handlers = [];
112 }
113 108
114 /**109 /*
115 * Dispatch stack information to all handlers.110 * Configure Raven with a DSN and extra options
116 * @param {Object.<string, *>} stack111 *
112 * @param {string} dsn The public Sentry DSN
113 * @param {object} options Optional set of of global options [optional]
114 * @return {Raven}
117 */115 */
118 function notifyHandlers(stack, isWindowError) {116 config: function(dsn, options) {
119 var exception = null;117 var self = this;
120 if (isWindowError && !TraceKit.collectWindowErrors) {118
121 return;119 if (this._globalServer) {
120 this._logDebug('error', 'Error: Raven has already been configured');
121 return this;
122 }122 }
123 for (var i in handlers) {123 if (!dsn) return this;
124 if (hasKey(handlers, i)) {124
125 try {125 // merge in options
126 handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));126 if (options) {
127 } catch (inner) {127 each(options, function(key, value){
128 exception = inner;128 // tags and extra are special and need to be put into context
129 if (key === 'tags' || key === 'extra') {
130 self._globalContext[key] = value;
131 } else {
132 self._globalOptions[key] = value;
129 }133 }
130 }134 });
131 }135 }
132 136
133 if (exception) {137 var uri = this._parseDSN(dsn),
134 throw exception;138 lastSlash = uri.path.lastIndexOf('/'),
135 }139 path = uri.path.substr(1, lastSlash);
136 }
137 140
138 var _oldOnerrorHandler, _onErrorHandlerInstalled;141 this._dsn = dsn;
139 142
140 /**143 // "Script error." is hard coded into browsers for errors that it can't read.
141 * Ensures all global unhandled exceptions are recorded.144 // this is the result of a script being pulled in from an external domain and CORS.
142 * Supported by Gecko and IE.145 this._globalOptions.ignoreErrors.push(/^Script error\.?$/);
143 * @param {string} message Error message.146 this._globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
144 * @param {string} url URL of script that generated the exception.
145 * @param {(number|string)} lineNo The line number at which the error
146 * occurred.
147 * @param {?(number|string)} colNo The column number at which the error
148 * occurred.
149 * @param {?Error} ex The actual Error object.
150 */
151 function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
152 var stack = null;
153 147
154 if (lastExceptionStack) {148 // join regexp rules into one big rule
155 TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);149 this._globalOptions.ignoreErrors = joinRegExp(this._globalOptions.ignoreErrors);
156 processLastException();150 this._globalOptions.ignoreUrls = this._globalOptions.ignoreUrls.length ? joinRegExp(this._globalOptions.ignoreUrls) : false;
157 } else if (ex) {151 this._globalOptions.whitelistUrls = this._globalOptions.whitelistUrls.length ? joinRegExp(this._globalOptions.whitelistUrls) : false;
158 // New chrome and blink send along a real error object152 this._globalOptions.includePaths = joinRegExp(this._globalOptions.includePaths);
159 // Let's just report that like a normal error.
160 // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
161 stack = TraceKit.computeStackTrace(ex);
162 notifyHandlers(stack, true);
163 } else {
164 var location = {
165 'url': url,
166 'line': lineNo,
167 'column': colNo
168 };
169 location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
170 location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
171 stack = {
172 'message': message,
173 'url': getLocationHref(),
174 'stack': [location]
175 };
176 notifyHandlers(stack, true);
177 }
178 153
179 if (_oldOnerrorHandler) {154 this._globalKey = uri.user;
180 return _oldOnerrorHandler.apply(this, arguments);155 this._globalSecret = uri.pass && uri.pass.substr(1);
181 }156 this._globalProject = uri.path.substr(lastSlash + 1);
182 157
183 return false;158 this._globalServer = this._getGlobalServer(uri);
184 }
185 159
186 function installGlobalHandler ()160 this._globalEndpoint = this._globalServer +
187 {161 '/' + path + 'api/' + this._globalProject + '/store/';
188 if (_onErrorHandlerInstalled) {162
189 return;163 if (this._globalOptions.fetchContext) {
164 TraceKit.remoteFetching = true;
190 }165 }
191 _oldOnerrorHandler = window.onerror;
192 window.onerror = traceKitWindowOnError;
193 _onErrorHandlerInstalled = true;
194 }
195 166
196 function uninstallGlobalHandler ()167 if (this._globalOptions.linesOfContext) {
197 {168 TraceKit.linesOfContext = this._globalOptions.linesOfContext;
198 if (!_onErrorHandlerInstalled) {
199 return;
200 }169 }
201 window.onerror = _oldOnerrorHandler;
202 _onErrorHandlerInstalled = false;
203 _oldOnerrorHandler = undefined;
204 }
205 170
206 function processLastException() {171 TraceKit.collectWindowErrors = !!this._globalOptions.collectWindowErrors;
207 var _lastExceptionStack = lastExceptionStack,
208 _lastArgs = lastArgs;
209 lastArgs = null;
210 lastExceptionStack = null;
211 lastException = null;
212 notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
213 }
214 172
215 /**173 // return for chaining
216 * Reports an unhandled Error to TraceKit.174 return this;
217 * @param {Error} ex175 },
218 * @param {?boolean} rethrow If false, do not re-throw the exception.176
219 * Only used for window.onerror to not cause an infinite loop of177 /*
220 * rethrowing.178 * Installs a global window.onerror error handler
179 * to capture and report uncaught exceptions.
180 * At this point, install() is required to be called due
181 * to the way TraceKit is set up.
182 *
183 * @return {Raven}
221 */184 */
222 function report(ex, rethrow) {185 install: function() {
223 var args = _slice.call(arguments, 1);186 var self = this;
224 if (lastExceptionStack) {187 if (this.isSetup() && !this._isRavenInstalled) {
225 if (lastException === ex) {188 TraceKit.report.subscribe(function () {
226 return; // already caught by an inner catch block, ignore189 self._handleOnErrorStackInfo.apply(self, arguments);
227 } else {190 });
228 processLastException();191 this._wrapBuiltIns();
229 }192
193 // Install all of the plugins
194 this._drainPlugins();
195
196 this._isRavenInstalled = true;
230 }197 }
231 198
232 var stack = TraceKit.computeStackTrace(ex);199 Error.stackTraceLimit = this._globalOptions.stackTraceLimit;
233 lastExceptionStack = stack;200 return this;
234 lastException = ex;201 },
235 lastArgs = args;
236
237 // If the stack trace is incomplete, wait for 2 seconds for
238 // slow slow IE to see if onerror occurs or not before reporting
239 // this exception; otherwise, we will end up with an incomplete
240 // stack trace
241 window.setTimeout(function () {
242 if (lastException === ex) {
243 processLastException();
244 }
245 }, (stack.incomplete ? 2000 : 0));
246 202
247 if (rethrow !== false) {203 /*
248 throw ex; // re-throw to propagate to the top level (and cause window.onerror)204 * Wrap code within a context so Raven can capture errors
205 * reliably across domains that is executed immediately.
206 *
207 * @param {object} options A specific set of options for this context [optional]
208 * @param {function} func The callback to be immediately executed within the context
209 * @param {array} args An array of arguments to be called with the callback [optional]
210 */
211 context: function(options, func, args) {
212 if (isFunction(options)) {
213 args = func || [];
214 func = options;
215 options = undefined;
249 }216 }
250 }
251
252 report.subscribe = subscribe;
253 report.unsubscribe = unsubscribe;
254 report.uninstall = unsubscribeAll;
255 return report;
256}());
257 217
258/**218 return this.wrap(options, func).apply(this, args);
259 * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript219 },
260 *
261 * Syntax:
262 * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
263 * Returns:
264 * s.name - exception name
265 * s.message - exception message
266 * s.stack[i].url - JavaScript or HTML file URL
267 * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
268 * s.stack[i].args - arguments passed to the function, if known
269 * s.stack[i].line - line number, if known
270 * s.stack[i].column - column number, if known
271 * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
272 *
273 * Supports:
274 * - Firefox: full stack trace with line numbers and unreliable column
275 * number on top frame
276 * - Opera 10: full stack trace with line and column numbers
277 * - Opera 9-: full stack trace with line numbers
278 * - Chrome: full stack trace with line and column numbers
279 * - Safari: line and column number for the topmost stacktrace element
280 * only
281 * - IE: no line numbers whatsoever
282 *
283 * Tries to guess names of anonymous functions by looking for assignments
284 * in the source code. In IE and Safari, we have to guess source file names
285 * by searching for function bodies inside all page scripts. This will not
286 * work for scripts that are loaded cross-domain.
287 * Here be dragons: some function names may be guessed incorrectly, and
288 * duplicate functions may be mismatched.
289 *
290 * TraceKit.computeStackTrace should only be used for tracing purposes.
291 * Logging of unhandled exceptions should be done with TraceKit.report,
292 * which builds on top of TraceKit.computeStackTrace and provides better
293 * IE support by utilizing the window.onerror event to retrieve information
294 * about the top of the stack.
295 *
296 * Note: In IE and Safari, no stack trace is recorded on the Error object,
297 * so computeStackTrace instead walks its *own* chain of callers.
298 * This means that:
299 * * in Safari, some methods may be missing from the stack trace;
300 * * in IE, the topmost function in the stack trace will always be the
301 * caller of computeStackTrace.
302 *
303 * This is okay for tracing (because you are likely to be calling
304 * computeStackTrace from the function you want to be the topmost element
305 * of the stack trace anyway), but not okay for logging unhandled
306 * exceptions (because your catch block will likely be far away from the
307 * inner function that actually caused the exception).
308 *
309 */
310TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
311 var sourceCache = {};
312 220
313 /**221 /*
314 * Attempts to retrieve source code via XMLHttpRequest, which is used222 * Wrap code within a context and returns back a new function to be executed
315 * to look up anonymous function names.223 *
316 * @param {string} url URL of source code.224 * @param {object} options A specific set of options for this context [optional]
317 * @return {string} Source contents.225 * @param {function} func The function to be wrapped in a new context
226 * @return {function} The newly wrapped functions with a context
318 */227 */
319 function loadSource(url) {228 wrap: function(options, func) {
320 if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.229 var self = this;
321 return '';230
231 // 1 argument has been passed, and it's not a function
232 // so just return it
233 if (isUndefined(func) && !isFunction(options)) {
234 return options;
322 }235 }
323 try {
324 var getXHR = function() {
325 try {
326 return new window.XMLHttpRequest();
327 } catch (e) {
328 // explicitly bubble up the exception if not found
329 return new window.ActiveXObject('Microsoft.XMLHTTP');
330 }
331 };
332 236
333 var request = getXHR();237 // options is optional
334 request.open('GET', url, false);238 if (isFunction(options)) {
335 request.send('');239 func = options;
336 return request.responseText;240 options = undefined;
337 } catch (e) {
338 return '';
339 }241 }
340 }
341 242
342 /**243 // At this point, we've passed along 2 arguments, and the second one
343 * Retrieves source code from the source code cache.244 // is not a function either, so we'll just return the second argument.
344 * @param {string} url URL of source code.245 if (!isFunction(func)) {
345 * @return {Array.<string>} Source contents.246 return func;
346 */247 }
347 function getSource(url) {248
348 if (!isString(url)) return [];249 // We don't wanna wrap it twice!
349 if (!hasKey(sourceCache, url)) {250 try {
350 // URL needs to be able to fetched within the acceptable domain. Otherwise,251 if (func.__raven__) {
351 // cross-domain errors will be triggered.252 return func;
352 var source = '';
353 var domain = '';
354 try { domain = document.domain; } catch (e) {}
355 if (url.indexOf(domain) !== -1) {
356 source = loadSource(url);
357 }253 }
358 sourceCache[url] = source ? source.split('\n') : [];254 } catch (e) {
255 // Just accessing the __raven__ prop in some Selenium environments
256 // can cause a "Permission denied" exception (see raven-js#495).
257 // Bail on wrapping and return the function as-is (defers to window.onerror).
258 return func;
359 }259 }
360 260
361 return sourceCache[url];261 // If this has already been wrapped in the past, return that
362 }262 if (func.__raven_wrapper__ ){
263 return func.__raven_wrapper__ ;
264 }
363 265
364 /**266 function wrapped() {
365 * Tries to use an externally loaded copy of source code to determine267 var args = [], i = arguments.length,
366 * the name of a function by looking at the name of the variable it was268 deep = !options || options && options.deep !== false;
367 * assigned to, if any.269 // Recursively wrap all of a function's arguments that are
368 * @param {string} url URL of source code.270 // functions themselves.
369 * @param {(string|number)} lineNo Line number in source code.
370 * @return {string} The function name, if discoverable.
371 */
372 function guessFunctionName(url, lineNo) {
373 var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
374 reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
375 line = '',
376 maxLines = 10,
377 source = getSource(url),
378 m;
379 271
380 if (!source.length) {272 while(i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
381 return UNKNOWN_FUNCTION;
382 }
383 273
384 // Walk backwards from the first line in the function until we find the line which274 try {
385 // matches the pattern above, which is the function definition275 return func.apply(this, args);
386 for (var i = 0; i < maxLines; ++i) {276 } catch(e) {
387 line = source[lineNo - i] + line;277 self._ignoreNextOnError();
278 self.captureException(e, options);
279 throw e;
280 }
281 }
388 282
389 if (!isUndefined(line)) {283 // copy over properties of the old function
390 if ((m = reGuessFunction.exec(line))) {284 for (var property in func) {
391 return m[1];285 if (hasKey(func, property)) {
392 } else if ((m = reFunctionArgNames.exec(line))) {286 wrapped[property] = func[property];
393 return m[1];
394 }
395 }287 }
396 }288 }
289 func.__raven_wrapper__ = wrapped;
397 290
398 return UNKNOWN_FUNCTION;291 wrapped.prototype = func.prototype;
399 }
400 292
401 /**293 // Signal that this function has been wrapped already
402 * Retrieves the surrounding lines from where an exception occurred.294 // for both debugging and to prevent it to being wrapped twice
403 * @param {string} url URL of source code.295 wrapped.__raven__ = true;
404 * @param {(string|number)} line Line number in source code to centre296 wrapped.__inner__ = func;
405 * around for context.
406 * @return {?Array.<string>} Lines of source code.
407 */
408 function gatherContext(url, line) {
409 var source = getSource(url);
410 297
411 if (!source.length) {298 return wrapped;
412 return null;299 },
413 }
414 300
415 var context = [],301 /*
416 // linesBefore & linesAfter are inclusive with the offending line.302 * Uninstalls the global error handler.
417 // if linesOfContext is even, there will be one extra line303 *
418 // *before* the offending line.304 * @return {Raven}
419 linesBefore = Math.floor(TraceKit.linesOfContext / 2),305 */
420 // Add one extra line if linesOfContext is odd306 uninstall: function() {
421 linesAfter = linesBefore + (TraceKit.linesOfContext % 2),307 TraceKit.report.uninstall();
422 start = Math.max(0, line - linesBefore - 1),
423 end = Math.min(source.length, line + linesAfter - 1);
424 308
425 line -= 1; // convert to 0-based index309 this._restoreBuiltIns();
426 310
427 for (var i = start; i < end; ++i) {311 Error.stackTraceLimit = this._originalErrorStackTraceLimit;
428 if (!isUndefined(source[i])) {312 this._isRavenInstalled = false;
429 context.push(source[i]);
430 }
431 }
432 313
433 return context.length > 0 ? context : null;314 return this;
434 }315 },
435 316
436 /**317 /*
437 * Escapes special characters, except for whitespace, in a string to be318 * Manually capture an exception and send it over to Sentry
438 * used inside a regular expression as a string literal.319 *
439 * @param {string} text The string.320 * @param {error} ex An exception to be logged
440 * @return {string} The escaped string literal.321 * @param {object} options A specific set of options for this error [optional]
441 */322 * @return {Raven}
442 function escapeRegExp(text) {
443 return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
444 }
445
446 /**
447 * Escapes special characters in a string to be used inside a regular
448 * expression as a string literal. Also ensures that HTML entities will
449 * be matched the same as their literal friends.
450 * @param {string} body The string.
451 * @return {string} The escaped string.
452 */323 */
453 function escapeCodeAsRegExpForMatchingInsideHTML(body) {324 captureException: function(ex, options) {
454 return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');325 // If not an Error is passed through, recall as a message instead
455 }326 if (!isError(ex)) return this.captureMessage(ex, options);
456 327
457 /**328 // Store the raw exception object for potential debugging and introspection
458 * Determines where a code fragment occurs in the source code.329 this._lastCapturedException = ex;
459 * @param {RegExp} re The function definition.
460 * @param {Array.<string>} urls A list of URLs to search.
461 * @return {?Object.<string, (string|number)>} An object containing
462 * the url, line, and column number of the defined function.
463 */
464 function findSourceInUrls(re, urls) {
465 var source, m;
466 for (var i = 0, j = urls.length; i < j; ++i) {
467 // console.log('searching', urls[i]);
468 if ((source = getSource(urls[i])).length) {
469 source = source.join('\n');
470 if ((m = re.exec(source))) {
471 // console.log('Found function in ' + urls[i]);
472 330
473 return {331 // TraceKit.report will re-raise any exception passed to it,
474 'url': urls[i],332 // which means you have to wrap it in try/catch. Instead, we
475 'line': source.substring(0, m.index).split('\n').length,333 // can wrap it here and only re-raise if TraceKit.report
476 'column': m.index - source.lastIndexOf('\n', m.index) - 1334 // raises an exception different from the one we asked to
477 };335 // report on.
478 }336 try {
337 var stack = TraceKit.computeStackTrace(ex);
338 this._handleStackInfo(stack, options);
339 } catch(ex1) {
340 if(ex !== ex1) {
341 throw ex1;
479 }342 }
480 }343 }
481 344
482 // console.log('no match');345 return this;
483 346 },
484 return null;
485 }
486 347
487 /**348 /*
488 * Determines at which column a code fragment occurs on a line of the349 * Manually send a message to Sentry
489 * source code.350 *
490 * @param {string} fragment The code fragment.351 * @param {string} msg A plain message to be captured in Sentry
491 * @param {string} url The URL to search.352 * @param {object} options A specific set of options for this message [optional]
492 * @param {(string|number)} line The line number to examine.353 * @return {Raven}
493 * @return {?number} The column number.
494 */354 */
495 function findSourceInLine(fragment, url, line) {355 captureMessage: function(msg, options) {
496 var source = getSource(url),356 // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
497 re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),357 // early call; we'll error on the side of logging anything called before configuration since it's
498 m;358 // probably something you should see:
359 if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(msg)) {
360 return;
361 }
499 362
500 line -= 1;363 // Fire away!
364 this._send(
365 objectMerge({
366 message: msg + '' // Make sure it's actually a string
367 }, options)
368 );
501 369
502 if (source && source.length > line && (m = re.exec(source[line]))) {370 return this;
503 return m.index;371 },
372
373 addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
374 var pluginArgs = Array.prototype.slice.call(arguments, 1);
375
376 this._plugins.push([plugin, pluginArgs]);
377 if (this._isRavenInstalled) {
378 this._drainPlugins();
504 }379 }
505 380
506 return null;381 return this;
507 }382 },
383
384 /*
385 * Set/clear a user to be sent along with the payload.
386 *
387 * @param {object} user An object representing user data [optional]
388 * @return {Raven}
389 */
390 setUserContext: function(user) {
391 // Intentionally do not merge here since that's an unexpected behavior.
392 this._globalContext.user = user;
393
394 return this;
395 },
396
397 /*
398 * Merge extra attributes to be sent along with the payload.
399 *
400 * @param {object} extra An object representing extra data [optional]
401 * @return {Raven}
402 */
403 setExtraContext: function(extra) {
404 this._mergeContext('extra', extra);
405
406 return this;
407 },
408
409 /*
410 * Merge tags to be sent along with the payload.
411 *
412 * @param {object} tags An object representing tags [optional]
413 * @return {Raven}
414 */
415 setTagsContext: function(tags) {
416 this._mergeContext('tags', tags);
417
418 return this;
419 },
420
421 /*
422 * Clear all of the context.
423 *
424 * @return {Raven}
425 */
426 clearContext: function() {
427 this._globalContext = {};
428
429 return this;
430 },
431
432 /*
433 * Get a copy of the current context. This cannot be mutated.
434 *
435 * @return {object} copy of context
436 */
437 getContext: function() {
438 // lol javascript
439 return JSON.parse(JSON.stringify(this._globalContext));
440 },
441
442 /*
443 * Set release version of application
444 *
445 * @param {string} release Typically something like a git SHA to identify version
446 * @return {Raven}
447 */
448 setRelease: function(release) {
449 this._globalOptions.release = release;
450
451 return this;
452 },
453
454 /*
455 * Set the dataCallback option
456 *
457 * @param {function} callback The callback to run which allows the
458 * data blob to be mutated before sending
459 * @return {Raven}
460 */
461 setDataCallback: function(callback) {
462 this._globalOptions.dataCallback = callback;
463
464 return this;
465 },
466
467 /*
468 * Set the shouldSendCallback option
469 *
470 * @param {function} callback The callback to run which allows
471 * introspecting the blob before sending
472 * @return {Raven}
473 */
474 setShouldSendCallback: function(callback) {
475 this._globalOptions.shouldSendCallback = callback;
476
477 return this;
478 },
508 479
509 /**480 /**
510 * Determines where a function was defined within the source code.481 * Override the default HTTP transport mechanism that transmits data
511 * @param {(Function|string)} func A function reference or serialized482 * to the Sentry server.
512 * function definition.483 *
513 * @return {?Object.<string, (string|number)>} An object containing484 * @param {function} transport Function invoked instead of the default
514 * the url, line, and column number of the defined function.485 * `makeRequest` handler.
486 *
487 * @return {Raven}
515 */488 */
516 function findSourceByFunctionBody(func) {489 setTransport: function(transport) {
517 if (typeof document === 'undefined')490 this._globalOptions.transport = transport;
518 return;
519 491
520 var urls = [window.location.href],492 return this;
521 scripts = document.getElementsByTagName('script'),493 },
522 body,
523 code = '' + func,
524 codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
525 eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
526 re,
527 parts,
528 result;
529 494
530 for (var i = 0; i < scripts.length; ++i) {495 /*
531 var script = scripts[i];496 * Get the latest raw exception that was captured by Raven.
532 if (script.src) {497 *
533 urls.push(script.src);498 * @return {error}
499 */
500 lastException: function() {
501 return this._lastCapturedException;
502 },
503
504 /*
505 * Get the last event id
506 *
507 * @return {string}
508 */
509 lastEventId: function() {
510 return this._lastEventId;
511 },
512
513 /*
514 * Determine if Raven is setup and ready to go.
515 *
516 * @return {boolean}
517 */
518 isSetup: function() {
519 if (!this._hasJSON) return false; // needs JSON support
520 if (!this._globalServer) {
521 if (!this.ravenNotConfiguredError) {
522 this.ravenNotConfiguredError = true;
523 this._logDebug('error', 'Error: Raven has not been configured.');
534 }524 }
525 return false;
535 }526 }
527 return true;
528 },
536 529
537 if (!(parts = codeRE.exec(code))) {530 afterLoad: function () {
538 re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));531 // TODO: remove window dependence?
532
533 // Attempt to initialize Raven on load
534 var RavenConfig = window.RavenConfig;
535 if (RavenConfig) {
536 this.config(RavenConfig.dsn, RavenConfig.config).install();
539 }537 }
538 },
540 539
541 // not sure if this is really necessary, but I don’t have a test540 showReportDialog: function (options) {
542 // corpus large enough to confirm that and it was in the original.541 if (!window.document) // doesn't work without a document (React native)
543 else {542 return;
544 var name = parts[1] ? '\\s+' + parts[1] : '',
545 args = parts[2].split(',').join('\\s*,\\s*');
546 543
547 body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');544 options = options || {};
548 re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');545
546 var lastEventId = options.eventId || this.lastEventId();
547 if (!lastEventId) {
548 throw new RavenConfigError('Missing eventId');
549 }549 }
550 550
551 // look for a normal function definition551 var dsn = options.dsn || this._dsn;
552 if ((result = findSourceInUrls(re, urls))) {552 if (!dsn) {
553 return result;553 throw new RavenConfigError('Missing DSN');
554 }554 }
555 555
556 // look for an old-school event handler function556 var encode = encodeURIComponent;
557 if ((parts = eventRE.exec(code))) {557 var qs = '';
558 var event = parts[1];558 qs += '?eventId=' + encode(lastEventId);
559 body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);559 qs += '&dsn=' + encode(dsn);
560 560
561 // look for a function defined in HTML as an onXXX handler561 var user = options.user || this._globalContext.user;
562 re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');562 if (user) {
563 if (user.name) qs += '&name=' + encode(user.name);
564 if (user.email) qs += '&email=' + encode(user.email);
565 }
563 566
564 if ((result = findSourceInUrls(re, urls[0]))) {567 var globalServer = this._getGlobalServer(this._parseDSN(dsn));
565 return result;
566 }
567 568
568 // look for ???569 var script = document.createElement('script');
569 re = new RegExp(body);570 script.async = true;
571 script.src = globalServer + '/api/embed/error-page/' + qs;
572 (document.head || document.body).appendChild(script);
573 },
570 574
571 if ((result = findSourceInUrls(re, urls))) {575 /**** Private functions ****/
572 return result;576 _ignoreNextOnError: function () {
573 }577 var self = this;
578 this._ignoreOnError += 1;
579 setTimeout(function () {
580 // onerror should trigger before setTimeout
581 self._ignoreOnError -= 1;
582 });
583 },
584
585 _triggerEvent: function(eventType, options) {
586 // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it
587 var evt, key;
588
589 if (!this._hasDocument)
590 return;
591
592 options = options || {};
593
594 eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1);
595
596 if (document.createEvent) {
597 evt = document.createEvent('HTMLEvents');
598 evt.initEvent(eventType, true, true);
599 } else {
600 evt = document.createEventObject();
601 evt.eventType = eventType;
574 }602 }
575 603
576 return null;604 for (key in options) if (hasKey(options, key)) {
577 }605 evt[key] = options[key];
606 }
578 607
579 // Contents of Exception in various browsers.608 if (document.createEvent) {
580 //609 // IE9 if standards
581 // SAFARI:610 document.dispatchEvent(evt);
582 // ex.message = Can't find variable: qq611 } else {
583 // ex.line = 59612 // IE8 regardless of Quirks or Standards
584 // ex.sourceId = 580238192613 // IE9 if quirks
585 // ex.sourceURL = http://...614 try {
586 // ex.expressionBeginOffset = 96615 document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
587 // ex.expressionCaretOffset = 98616 } catch(e) {
588 // ex.expressionEndOffset = 98617 // Do nothing
589 // ex.name = ReferenceError618 }
590 //619 }
591 // FIREFOX:620 },
592 // ex.message = qq is not defined
593 // ex.fileName = http://...
594 // ex.lineNumber = 59
595 // ex.columnNumber = 69
596 // ex.stack = ...stack trace... (see the example below)
597 // ex.name = ReferenceError
598 //
599 // CHROME:
600 // ex.message = qq is not defined
601 // ex.name = ReferenceError
602 // ex.type = not_defined
603 // ex.arguments = ['aa']
604 // ex.stack = ...stack trace...
605 //
606 // INTERNET EXPLORER:
607 // ex.message = ...
608 // ex.name = ReferenceError
609 //
610 // OPERA:
611 // ex.message = ...message... (see the example below)
612 // ex.name = ReferenceError
613 // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
614 // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
615 621
616 /**622 /**
617 * Computes stack trace information from the stack property.623 * Install any queued plugins
618 * Chrome and Gecko use this property.
619 * @param {Error} ex
620 * @return {?Object.<string, *>} Stack trace information.
621 */624 */
622 function computeStackTraceFromStackProp(ex) {625 _wrapBuiltIns: function() {
623 if (isUndefined(ex.stack) || !ex.stack) return;626 var self = this;
627
628 function fill(obj, name, replacement, noUndo) {
629 var orig = obj[name];
630 obj[name] = replacement(orig);
631 if (!noUndo) {
632 self._wrappedBuiltIns.push([obj, name, orig]);
633 }
634 }
624 635
625 var chrome = /^\s*at (.*?) ?\(?((?:(?:file|https?|chrome-extension):.*?)|<anonymous>):(\d+)(?::(\d+))?\)?\s*$/i,636 function wrapTimeFn(orig) {
626 gecko = /^\s*(.*?)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i,637 return function (fn, t) { // preserve arity
627 winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,638 // Make a copy of the arguments
628 lines = ex.stack.split('\n'),639 var args = [].slice.call(arguments);
629 stack = [],640 var originalCallback = args[0];
630 parts,641 if (isFunction(originalCallback)) {
631 element,642 args[0] = self.wrap(originalCallback);
632 reference = /^(.*) is undefined$/.exec(ex.message);643 }
633 644
634 for (var i = 0, j = lines.length; i < j; ++i) {645 // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
635 if ((parts = gecko.exec(lines[i]))) {646 // also supports only two arguments and doesn't care what this is, so we
636 element = {647 // can just call the original function directly.
637 'url': parts[3],648 if (orig.apply) {
638 'func': parts[1] || UNKNOWN_FUNCTION,649 return orig.apply(this, args);
639 'args': parts[2] ? parts[2].split(',') : '',650 } else {
640 'line': +parts[4],651 return orig(args[0], args[1]);
641 'column': parts[5] ? +parts[5] : null652 }
653 };
654 }
655
656 fill(window, 'setTimeout', wrapTimeFn);
657 fill(window, 'setInterval', wrapTimeFn);
658 if (window.requestAnimationFrame) {
659 fill(window, 'requestAnimationFrame', function (orig) {
660 return function (cb) {
661 return orig(self.wrap(cb));
642 };662 };
643 } else if ((parts = chrome.exec(lines[i]))) {663 });
644 element = {664 }
645 'url': parts[2],665
646 'func': parts[1] || UNKNOWN_FUNCTION,666 // event targets borrowed from bugsnag-js:
647 'line': +parts[3],667 // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
648 'column': parts[4] ? +parts[4] : null668 'EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload'.replace(/\w+/g, function (global) {
669 var proto = window[global] && window[global].prototype;
670 if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {
671 fill(proto, 'addEventListener', function(orig) {
672 return function (evt, fn, capture, secure) { // preserve arity
673 try {
674 if (fn && fn.handleEvent) {
675 fn.handleEvent = self.wrap(fn.handleEvent);
676 }
677 } catch (err) {
678 // can sometimes get 'Permission denied to access property "handle Event'
679 }
680 return orig.call(this, evt, self.wrap(fn), capture, secure);
681 };
682 });
683 fill(proto, 'removeEventListener', function (orig) {
684 return function (evt, fn, capture, secure) {
685 fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);
686 return orig.call(this, evt, fn, capture, secure);
687 };
688 });
689 }
690 });
691
692 if ('XMLHttpRequest' in window) {
693 fill(XMLHttpRequest.prototype, 'send', function(origSend) {
694 return function (data) { // preserve arity
695 var xhr = this;
696 'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
697 if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
698 fill(xhr, prop, function (orig) {
699 return self.wrap(orig);
700 }, true /* noUndo */); // don't track filled methods on XHR instances
701 }
702 });
703 return origSend.apply(this, arguments);
649 };704 };
650 } else if ((parts = winjs.exec(lines[i]))) {705 });
651 element = {706 }
652 'url': parts[2],707
653 'func': parts[1] || UNKNOWN_FUNCTION,708 var $ = window.jQuery || window.$;
654 'line': +parts[3],709 if ($ && $.fn && $.fn.ready) {
655 'column': parts[4] ? +parts[4] : null710 fill($.fn, 'ready', function (orig) {
711 return function (fn) {
712 return orig.call(this, self.wrap(fn));
656 };713 };
657 } else {714 });
658 continue;715 }
659 }716 },
660 717
661 if (!element.func && element.line) {718 _restoreBuiltIns: function () {
662 element.func = guessFunctionName(element.url, element.line);719 // restore any wrapped builtins
663 }720 var builtin;
721 while (this._wrappedBuiltIns.length) {
722 builtin = this._wrappedBuiltIns.shift();
664 723
665 if (element.line) {724 var obj = builtin[0],
666 element.context = gatherContext(element.url, element.line);725 name = builtin[1],
667 }726 orig = builtin[2];
668 727
669 stack.push(element);728 obj[name] = orig;
670 }729 }
730 },
671 731
672 if (!stack.length) {732 _drainPlugins: function() {
673 return null;733 var self = this;
734
735 // FIX ME TODO
736 each(this._plugins, function(_, plugin) {
737 var installer = plugin[0];
738 var args = plugin[1];
739 installer.apply(self, [self].concat(args));
740 });
741 },
742
743 _parseDSN: function(str) {
744 var m = dsnPattern.exec(str),
745 dsn = {},
746 i = 7;
747
748 try {
749 while (i--) dsn[dsnKeys[i]] = m[i] || '';
750 } catch(e) {
751 throw new RavenConfigError('Invalid DSN: ' + str);
674 }752 }
675 753
676 if (stack[0].line && !stack[0].column && reference) {754 if (dsn.pass && !this._globalOptions.allowSecretKey) {
677 stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);755 throw new RavenConfigError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key');
678 } else if (!stack[0].column && !isUndefined(ex.columnNumber)) {
679 // FireFox uses this awesome columnNumber property for its top frame
680 // Also note, Firefox's column number is 0-based and everything else expects 1-based,
681 // so adding 1
682 stack[0].column = ex.columnNumber + 1;
683 }756 }
684 757
685 return {758 return dsn;
686 'name': ex.name,759 },
687 'message': ex.message,
688 'url': getLocationHref(),
689 'stack': stack
690 };
691 }
692 760
693 /**761 _getGlobalServer: function(uri) {
694 * Computes stack trace information from the stacktrace property.762 // assemble the endpoint from the uri pieces
695 * Opera 10 uses this property.763 var globalServer = '//' + uri.host +
696 * @param {Error} ex764 (uri.port ? ':' + uri.port : '');
697 * @return {?Object.<string, *>} Stack trace information.
698 */
699 function computeStackTraceFromStacktraceProp(ex) {
700 // Access and store the stacktrace property before doing ANYTHING
701 // else to it because Opera is not very good at providing it
702 // reliably in other circumstances.
703 var stacktrace = ex.stacktrace;
704 if (isUndefined(ex.stacktrace) || !ex.stacktrace) return;
705 765
706 var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,766 if (uri.protocol) {
707 lines = stacktrace.split('\n'),767 globalServer = uri.protocol + ':' + globalServer;
708 stack = [],768 }
709 parts;769 return globalServer;
770 },
710 771
711 for (var i = 0, j = lines.length; i < j; i += 2) {772 _handleOnErrorStackInfo: function() {
712 if ((parts = testRE.exec(lines[i]))) {773 // if we are intentionally ignoring errors via onerror, bail out
713 var element = {774 if (!this._ignoreOnError) {
714 'line': +parts[1],775 this._handleStackInfo.apply(this, arguments);
715 'column': +parts[2],776 }
716 'func': parts[3] || parts[4],777 },
717 'args': parts[5] ? parts[5].split(',') : [],
718 'url': parts[6]
719 };
720 778
721 if (!element.func && element.line) {779 _handleStackInfo: function(stackInfo, options) {
722 element.func = guessFunctionName(element.url, element.line);780 var self = this;
723 }781 var frames = [];
724 if (element.line) {
725 try {
726 element.context = gatherContext(element.url, element.line);
727 } catch (exc) {}
728 }
729 782
730 if (!element.context) {783 if (stackInfo.stack && stackInfo.stack.length) {
731 element.context = [lines[i + 1]];784 each(stackInfo.stack, function(i, stack) {
785 var frame = self._normalizeFrame(stack);
786 if (frame) {
787 frames.push(frame);
732 }788 }
789 });
790 }
733 791
734 stack.push(element);792 this._triggerEvent('handle', {
793 stackInfo: stackInfo,
794 options: options
795 });
796
797 this._processException(
798 stackInfo.name,
799 stackInfo.message,
800 stackInfo.url,
801 stackInfo.lineno,
802 frames.slice(0, this._globalOptions.stackTraceLimit),
803 options
804 );
805 },
806
807 _normalizeFrame: function(frame) {
808 if (!frame.url) return;
809
810 // normalize the frames data
811 var normalized = {
812 filename: frame.url,
813 lineno: frame.line,
814 colno: frame.column,
815 'function': frame.func || '?'
816 }, context = this._extractContextFromFrame(frame), i;
817
818 if (context) {
819 var keys = ['pre_context', 'context_line', 'post_context'];
820 i = 3;
821 while (i--) normalized[keys[i]] = context[i];
822 }
823
824 normalized.in_app = !( // determine if an exception came from outside of our app
825 // first we check the global includePaths list.
826 !!this._globalOptions.includePaths.test && !this._globalOptions.includePaths.test(normalized.filename) ||
827 // Now we check for fun, if the function name is Raven or TraceKit
828 /(Raven|TraceKit)\./.test(normalized['function']) ||
829 // finally, we do a last ditch effort and check for raven.min.js
830 /raven\.(min\.)?js$/.test(normalized.filename)
831 );
832
833 return normalized;
834 },
835
836 _extractContextFromFrame: function(frame) {
837 // immediately check if we should even attempt to parse a context
838 if (!frame.context || !this._globalOptions.fetchContext) return;
839
840 var context = frame.context,
841 pivot = ~~(context.length / 2),
842 i = context.length, isMinified = false;
843
844 while (i--) {
845 // We're making a guess to see if the source is minified or not.
846 // To do that, we make the assumption if *any* of the lines passed
847 // in are greater than 300 characters long, we bail.
848 // Sentry will see that there isn't a context
849 if (context[i].length > 300) {
850 isMinified = true;
851 break;
735 }852 }
736 }853 }
737 854
738 if (!stack.length) {855 if (isMinified) {
739 return null;856 // The source is minified and we don't know which column. Fuck it.
857 if (isUndefined(frame.column)) return;
858
859 // If the source is minified and has a frame column
860 // we take a chunk of the offending line to hopefully shed some light
861 return [
862 [], // no pre_context
863 context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column
864 [] // no post_context
865 ];
740 }866 }
741 867
742 return {868 return [
743 'name': ex.name,869 context.slice(0, pivot), // pre_context
744 'message': ex.message,870 context[pivot], // context_line
745 'url': getLocationHref(),871 context.slice(pivot + 1) // post_context
746 'stack': stack872 ];
747 };873 },
748 }
749 874
750 /**875 _processException: function(type, message, fileurl, lineno, frames, options) {
751 * NOT TESTED.876 var stacktrace, fullMessage;
752 * Computes stack trace information from an error message that includes877
753 * the stack trace.878 if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return;
754 * Opera 9 and earlier use this method if the option to show stack879
755 * traces is turned on in opera:config.880 message += '';
756 * @param {Error} ex881 message = truncate(message, this._globalOptions.maxMessageLength);
757 * @return {?Object.<string, *>} Stack information.882
758 */883 fullMessage = (type ? type + ': ' : '') + message;
759 function computeStackTraceFromOperaMultiLineMessage(ex) {884 fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength);
760 // Opera includes a stack trace into the exception message. An example is:885
761 //886 if (frames && frames.length) {
762 // Statement on line 3: Undefined variable: undefinedFunc887 fileurl = frames[0].filename || fileurl;
763 // Backtrace:888 // Sentry expects frames oldest to newest
764 // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz889 // and JS sends them as newest to oldest
765 // undefinedFunc(a);890 frames.reverse();
766 // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy891 stacktrace = {frames: frames};
767 // zzz(x, y, z);892 } else if (fileurl) {
768 // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx893 stacktrace = {
769 // yyy(a, a, a);894 frames: [{
770 // Line 1 of function script895 filename: fileurl,
771 // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }896 lineno: lineno,
772 // ...897 in_app: true
898 }]
899 };
900 }
773 901
774 var lines = ex.message.split('\n');902 if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return;
775 if (lines.length < 4) {903 if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return;
776 return null;904
905 var data = objectMerge({
906 // sentry.interfaces.Exception
907 exception: {
908 values: [{
909 type: type,
910 value: message,
911 stacktrace: stacktrace
912 }]
913 },
914 culprit: fileurl,
915 message: fullMessage
916 }, options);
917
918 // Fire away!
919 this._send(data);
920 },
921
922 _trimPacket: function(data) {
923 // For now, we only want to truncate the two different messages
924 // but this could/should be expanded to just trim everything
925 var max = this._globalOptions.maxMessageLength;
926 data.message = truncate(data.message, max);
927 if (data.exception) {
928 var exception = data.exception.values[0];
929 exception.value = truncate(exception.value, max);
777 }930 }
778 931
779 var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,932 return data;
780 lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,933 },
781 lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
782 stack = [],
783 scripts = document.getElementsByTagName('script'),
784 inlineScriptBlocks = [],
785 parts,
786 i,
787 len,
788 source;
789 934
790 for (i in scripts) {935 _getHttpData: function() {
791 if (hasKey(scripts, i) && !scripts[i].src) {936 if (!this._hasDocument || !document.location || !document.location.href) {
792 inlineScriptBlocks.push(scripts[i]);937 return;
793 }
794 }938 }
795 939
796 for (i = 2, len = lines.length; i < len; i += 2) {940 var httpData = {
797 var item = null;941 headers: {
798 if ((parts = lineRE1.exec(lines[i]))) {942 'User-Agent': navigator.userAgent
799 item = {
800 'url': parts[2],
801 'func': parts[3],
802 'line': +parts[1]
803 };
804 } else if ((parts = lineRE2.exec(lines[i]))) {
805 item = {
806 'url': parts[3],
807 'func': parts[4]
808 };
809 var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
810 var script = inlineScriptBlocks[parts[2] - 1];
811 if (script) {
812 source = getSource(item.url);
813 if (source) {
814 source = source.join('\n');
815 var pos = source.indexOf(script.innerText);
816 if (pos >= 0) {
817 item.line = relativeLine + source.substring(0, pos).split('\n').length;
818 }
819 }
820 }
821 } else if ((parts = lineRE3.exec(lines[i]))) {
822 var url = window.location.href.replace(/#.*$/, ''),
823 line = parts[1];
824 var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1]));
825 source = findSourceInUrls(re, [url]);
826 item = {
827 'url': url,
828 'line': source ? source.line : line,
829 'func': ''
830 };
831 }943 }
944 };
832 945
833 if (item) {946 httpData.url = document.location.href;
834 if (!item.func) {947
835 item.func = guessFunctionName(item.url, item.line);948 if (document.referrer) {
836 }949 httpData.headers.Referer = document.referrer;
837 var context = gatherContext(item.url, item.line);
838 var midline = (context ? context[Math.floor(context.length / 2)] : null);
839 if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) {
840 item.context = context;
841 } else {
842 // 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);
843 item.context = [lines[i + 1]];
844 }
845 stack.push(item);
846 }
847 }
848 if (!stack.length) {
849 return null; // could not parse multiline exception message as Opera stack trace
850 }950 }
851 951
852 return {952 return httpData;
853 'name': ex.name,953 },
854 'message': lines[0],
855 'url': getLocationHref(),
856 'stack': stack
857 };
858 }
859 954
860 /**
861 * Adds information about the first frame to incomplete stack traces.
862 * Safari and IE require this to get complete data on the first frame.
863 * @param {Object.<string, *>} stackInfo Stack trace information from
864 * one of the compute* methods.
865 * @param {string} url The URL of the script that caused an error.
866 * @param {(number|string)} lineNo The line number of the script that
867 * caused an error.
868 * @param {string=} message The error generated by the browser, which
869 * hopefully contains the name of the object that caused the error.
870 * @return {boolean} Whether or not the stack information was
871 * augmented.
872 */
873 function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
874 var initial = {
875 'url': url,
876 'line': lineNo
877 };
878 955
879 if (initial.url && initial.line) {956 _send: function(data) {
880 stackInfo.incomplete = false;957 var self = this;
881 958
882 if (!initial.func) {959 var globalOptions = this._globalOptions;
883 initial.func = guessFunctionName(initial.url, initial.line);
884 }
885 960
886 if (!initial.context) {961 var baseData = {
887 initial.context = gatherContext(initial.url, initial.line);962 project: this._globalProject,
888 }963 logger: globalOptions.logger,
964 platform: 'javascript'
965 }, httpData = this._getHttpData();
889 966
890 var reference = / '([^']+)' /.exec(message);967 if (httpData) {
891 if (reference) {968 baseData.request = httpData;
892 initial.column = findSourceInLine(reference[1], initial.url, initial.line);969 }
893 }
894 970
895 if (stackInfo.stack.length > 0) {971 data = objectMerge(baseData, data);
896 if (stackInfo.stack[0].url === initial.url) {
897 if (stackInfo.stack[0].line === initial.line) {
898 return false; // already in stack trace
899 } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
900 stackInfo.stack[0].line = initial.line;
901 stackInfo.stack[0].context = initial.context;
902 return false;
903 }
904 }
905 }
906 972
907 stackInfo.stack.unshift(initial);973 // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
908 stackInfo.partial = true;974 data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags);
909 return true;975 data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra);
910 } else {
911 stackInfo.incomplete = true;
912 }
913 976
914 return false;977 // Send along our own collected metadata with extra
915 }978 data.extra['session:duration'] = now() - this._startTime;
916 979
917 /**980 // If there are no tags/extra, strip the key from the payload alltogther.
918 * Computes stack trace information by walking the arguments.caller981 if (isEmptyObject(data.tags)) delete data.tags;
919 * chain at the time the exception occurred. This will cause earlier
920 * frames to be missed but is the only way to get any stack trace in
921 * Safari and IE. The top frame is restored by
922 * {@link augmentStackTraceWithInitialElement}.
923 * @param {Error} ex
924 * @return {?Object.<string, *>} Stack trace information.
925 */
926 function computeStackTraceByWalkingCallerChain(ex, depth) {
927 var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
928 stack = [],
929 funcs = {},
930 recursion = false,
931 parts,
932 item,
933 source;
934 982
935 for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {983 if (this._globalContext.user) {
936 if (curr === computeStackTrace || curr === TraceKit.report) {984 // sentry.interfaces.User
937 // console.log('skipping internal function');985 data.user = this._globalContext.user;
938 continue;986 }
939 }
940 987
941 item = {988 // Include the release if it's defined in globalOptions
942 'url': null,989 if (globalOptions.release) data.release = globalOptions.release;
943 'func': UNKNOWN_FUNCTION,
944 'line': null,
945 'column': null
946 };
947 990
948 if (curr.name) {991 // Include server_name if it's defined in globalOptions
949 item.func = curr.name;992 if (globalOptions.serverName) data.server_name = globalOptions.serverName;
950 } else if ((parts = functionName.exec(curr.toString()))) {
951 item.func = parts[1];
952 }
953 993
954 if (typeof item.func === 'undefined') {994 if (isFunction(globalOptions.dataCallback)) {
955 try {995 data = globalOptions.dataCallback(data) || data;
956 item.func = parts.input.substring(0, parts.input.indexOf('{'));996 }
957 } catch (e) { }
958 }
959 997
960 if ((source = findSourceByFunctionBody(curr))) {998 // Why??????????
961 item.url = source.url;999 if (!data || isEmptyObject(data)) {
962 item.line = source.line;1000 return;
1001 }
963 1002
964 if (item.func === UNKNOWN_FUNCTION) {1003 // Check if the request should be filtered or not
965 item.func = guessFunctionName(item.url, item.line);1004 if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) {
966 }1005 return;
1006 }
967 1007
968 var reference = / '([^']+)' /.exec(ex.message || ex.description);1008 // Send along an event_id if not explicitly passed.
969 if (reference) {1009 // This event_id can be used to reference the error within Sentry itself.
970 item.column = findSourceInLine(reference[1], source.url, source.line);1010 // Set lastEventId after we know the error should actually be sent
971 }1011 this._lastEventId = data.event_id || (data.event_id = uuid4());
972 }
973 1012
974 if (funcs['' + curr]) {1013 // Try and clean up the packet before sending by truncating long values
975 recursion = true;1014 data = this._trimPacket(data);
976 }else{
977 funcs['' + curr] = true;
978 }
979 1015
980 stack.push(item);1016 this._logDebug('debug', 'Raven about to send:', data);
981 }
982 1017
983 if (depth) {1018 if (!this.isSetup()) return;
984 // console.log('depth is ' + depth);
985 // console.log('stack is ' + stack.length);
986 stack.splice(0, depth);
987 }
988 1019
989 var result = {1020 var auth = {
990 'name': ex.name,1021 sentry_version: '7',
991 'message': ex.message,1022 sentry_client: 'raven-js/' + this.VERSION,
992 'url': getLocationHref(),1023 sentry_key: this._globalKey
993 'stack': stack
994 };1024 };
995 augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);1025 if (this._globalSecret) {
996 return result;1026 auth.sentry_secret = this._globalSecret;
997 }1027 }
998
999 /**
1000 * Computes a stack trace for an exception.
1001 * @param {Error} ex
1002 * @param {(string|number)=} depth
1003 */
1004 function computeStackTrace(ex, depth) {
1005 var stack = null;
1006 depth = (depth == null ? 0 : +depth);
1007 1028
1008 try {1029 var url = this._globalEndpoint;
1009 // This must be tried first because Opera 10 *destroys*1030 (globalOptions.transport || this._makeRequest).call(this, {
1010 // its stacktrace property if you try to access the stack1031 url: url,
1011 // property first!!1032 auth: auth,
1012 stack = computeStackTraceFromStacktraceProp(ex);1033 data: data,
1013 if (stack) {1034 options: globalOptions,
1014 return stack;1035 onSuccess: function success() {
1015 }1036 self._triggerEvent('success', {
1016 } catch (e) {1037 data: data,
1017 if (TraceKit.debug) {1038 src: url
1018 throw e;1039 });
1040 },
1041 onError: function failure() {
1042 self._triggerEvent('failure', {
1043 data: data,
1044 src: url
1045 });
1019 }1046 }
1047 });
1048 },
1049
1050 _makeImageRequest: function(opts) {
1051 // Tack on sentry_data to auth options, which get urlencoded
1052 opts.auth.sentry_data = JSON.stringify(opts.data);
1053
1054 var img = this._newImage(),
1055 src = opts.url + '?' + urlencode(opts.auth),
1056 crossOrigin = opts.options.crossOrigin;
1057
1058 if (crossOrigin || crossOrigin === '') {
1059 img.crossOrigin = crossOrigin;
1020 }1060 }
1061 img.onload = opts.onSuccess;
1062 img.onerror = img.onabort = opts.onError;
1063 img.src = src;
1064 },
1021 1065
1022 try {1066 _makeXhrRequest: function(opts) {
1023 stack = computeStackTraceFromStackProp(ex);1067 var request;
1024 if (stack) {1068
1025 return stack;1069 var url = opts.url;
1026 }1070 function handler() {
1027 } catch (e) {1071 if (request.status === 200) {
1028 if (TraceKit.debug) {1072 if (opts.onSuccess) {
1029 throw e;1073 opts.onSuccess();
1074 }
1075 } else if (opts.onError) {
1076 opts.onError();
1030 }1077 }
1031 }1078 }
1032 1079
1033 try {1080 request = new XMLHttpRequest();
1034 stack = computeStackTraceFromOperaMultiLineMessage(ex);1081 if ('withCredentials' in request) {
1035 if (stack) {1082 request.onreadystatechange = function () {
1036 return stack;1083 if (request.readyState !== 4) {
1037 }1084 return;
1038 } catch (e) {1085 }
1039 if (TraceKit.debug) {1086 handler();
1040 throw e;1087 };
1041 }1088 } else {
1089 request = new XDomainRequest();
1090 // xdomainrequest cannot go http -> https (or vice versa),
1091 // so always use protocol relative
1092 url = url.replace(/^https?:/, '');
1093
1094 // onreadystatechange not supported by XDomainRequest
1095 request.onload = handler;
1042 }1096 }
1043 1097
1044 try {1098 // NOTE: auth is intentionally sent as part of query string (NOT as custom
1045 stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);1099 // HTTP header) so as to avoid preflight CORS requests
1046 if (stack) {1100 request.open('POST', url + '?' + urlencode(opts.auth));
1047 return stack;1101 request.send(JSON.stringify(opts.data));
1048 }1102 },
1049 } catch (e) {1103
1050 if (TraceKit.debug) {1104 _makeRequest: function(opts) {
1051 throw e;1105 var hasCORS =
1052 }1106 'withCredentials' in new XMLHttpRequest() ||
1107 typeof XDomainRequest !== 'undefined';
1108
1109 return (hasCORS ? this._makeXhrRequest : this._makeImageRequest)(opts);
1110 },
1111
1112 // Note: this is shitty, but I can't figure out how to get
1113 // sinon to stub document.createElement without breaking everything
1114 // so this wrapper is just so I can stub it for tests.
1115 _newImage: function() {
1116 return document.createElement('img');
1117 },
1118
1119 _logDebug: function(level) {
1120 if (this._originalConsoleMethods[level] && this.debug) {
1121 // In IE<10 console methods do not have their own 'apply' method
1122 Function.prototype.apply.call(
1123 this._originalConsoleMethods[level],
1124 this._originalConsole,
1125 [].slice.call(arguments, 1)
1126 );
1053 }1127 }
1128 },
1054 1129
1055 return {1130 _mergeContext: function(key, context) {
1056 'name': ex.name,1131 if (isUndefined(context)) {
1057 'message': ex.message,1132 delete this._globalContext[key];
1058 'url': getLocationHref()1133 } else {
1059 };1134 this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context);
1135 }
1060 }1136 }
1137};
1061 1138
1062 computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;1139// Deprecations
1063 computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp;1140Raven.prototype.setUser = Raven.prototype.setUserContext;
1064 computeStackTrace.guessFunctionName = guessFunctionName;1141Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
1065 computeStackTrace.gatherContext = gatherContext;
1066 1142
1067 return computeStackTrace;1143module.exports = Raven;
1068}());1144
1145},{"1":1,"4":4,"5":5}],3:[function(_dereq_,module,exports){
1146/**
1147 * Enforces a single instance of the Raven client, and the
1148 * main entry point for Raven. If you are a consumer of the
1149 * Raven library, you SHOULD load this file (vs raven.js).
1150 **/
1069 1151
1070'use strict';1152'use strict';
1071 1153
1072// First, check for JSON support1154var RavenConstructor = _dereq_(2);
1073// If there is no JSON, we no-op the core features of Raven1155
1074// since JSON is required to encode the payload1156var _Raven = window.Raven;
1075var _Raven = window.Raven,1157
1076 hasJSON = !!(typeof JSON === 'object' && JSON.stringify),1158var Raven = new RavenConstructor();
1077 // Raven can run in contexts where there's no document (react-native)
1078 hasDocument = typeof document !== 'undefined',
1079 lastCapturedException,
1080 lastEventId,
1081 globalServer,
1082 globalKey,
1083 globalProject,
1084 globalContext = {},
1085 globalOptions = {
1086 logger: 'javascript',
1087 ignoreErrors: [],
1088 ignoreUrls: [],
1089 whitelistUrls: [],
1090 includePaths: [],
1091 crossOrigin: 'anonymous',
1092 collectWindowErrors: true,
1093 maxMessageLength: 100
1094 },
1095 isRavenInstalled = false,
1096 objectPrototype = Object.prototype,
1097 // capture references to window.console *and* all its methods first
1098 // before the console plugin has a chance to monkey patch
1099 originalConsole = window.console || {},
1100 originalConsoleMethods = {},
1101 plugins = [],
1102 startTime = now();
1103 1159
1104for (var method in originalConsole) {
1105 originalConsoleMethods[method] = originalConsole[method];
1106}
1107/*1160/*
1108 * The core Raven singleton1161 * Allow multiple versions of Raven to be installed.
1162 * Strip Raven from the global context and returns the instance.
1109 *1163 *
1110 * @this {Raven}1164 * @return {Raven}
1111 */1165 */
1112var Raven = {1166Raven.noConflict = function () {
1113 VERSION: '1.3.0',1167 window.Raven = _Raven;
1168 return Raven;
1169};
1114 1170
1115 debug: false,1171Raven.afterLoad();
1116 1172
1117 /*1173module.exports = Raven;
1118 * Allow multiple versions of Raven to be installed.
1119 * Strip Raven from the global context and returns the instance.
1120 *
1121 * @return {Raven}
1122 */
1123 noConflict: function() {
1124 window.Raven = _Raven;
1125 return Raven;
1126 },
1127 1174
1128 /*1175},{"2":2}],4:[function(_dereq_,module,exports){
1129 * Configure Raven with a DSN and extra options1176'use strict';
1130 *
1131 * @param {string} dsn The public Sentry DSN
1132 * @param {object} options Optional set of of global options [optional]
1133 * @return {Raven}
1134 */
1135 config: function(dsn, options) {
1136 if (globalServer) {
1137 logDebug('error', 'Error: Raven has already been configured');
1138 return Raven;
1139 }
1140 if (!dsn) return Raven;
1141 1177
1142 var uri = parseDSN(dsn),1178var objectPrototype = Object.prototype;
1143 lastSlash = uri.path.lastIndexOf('/'),
1144 path = uri.path.substr(1, lastSlash);
1145 1179
1146 // merge in options1180function isUndefined(what) {
1147 if (options) {1181 return what === void 0;
1148 each(options, function(key, value){1182}
1149 // tags and extra are special and need to be put into context
1150 if (key == 'tags' || key == 'extra') {
1151 globalContext[key] = value;
1152 } else {
1153 globalOptions[key] = value;
1154 }
1155 });
1156 }
1157 1183
1158 // "Script error." is hard coded into browsers for errors that it can't read.1184function isFunction(what) {
1159 // this is the result of a script being pulled in from an external domain and CORS.1185 return typeof what === 'function';
1160 globalOptions.ignoreErrors.push(/^Script error\.?$/);1186}
1161 globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
1162 1187
1163 // join regexp rules into one big rule1188function isString(what) {
1164 globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);1189 return objectPrototype.toString.call(what) === '[object String]';
1165 globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false;1190}
1166 globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false;
1167 globalOptions.includePaths = joinRegExp(globalOptions.includePaths);
1168 1191
1169 globalKey = uri.user;1192function isObject(what) {
1170 globalProject = uri.path.substr(lastSlash + 1);1193 return typeof what === 'object' && what !== null;
1194}
1171 1195
1172 // assemble the endpoint from the uri pieces1196function isEmptyObject(what) {
1173 globalServer = '//' + uri.host +1197 for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars
1174 (uri.port ? ':' + uri.port : '') +1198 return true;
1175 '/' + path + 'api/' + globalProject + '/store/';1199}
1176 1200
1177 if (uri.protocol) {1201// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
1178 globalServer = uri.protocol + ':' + globalServer;1202// with some tiny modifications
1179 }1203function isError(what) {
1204 var toString = objectPrototype.toString.call(what);
1205 return isObject(what) &&
1206 toString === '[object Error]' ||
1207 toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions
1208 what instanceof Error;
1209}
1180 1210
1181 if (globalOptions.fetchContext) {1211function each(obj, callback) {
1182 TraceKit.remoteFetching = true;1212 var i, j;
1183 }
1184 1213
1185 if (globalOptions.linesOfContext) {1214 if (isUndefined(obj.length)) {
1186 TraceKit.linesOfContext = globalOptions.linesOfContext;1215 for (i in obj) {
1216 if (hasKey(obj, i)) {
1217 callback.call(null, i, obj[i]);
1218 }
1187 }1219 }
1188 1220 } else {
1189 TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;1221 j = obj.length;
1190 1222 if (j) {
1191 // return for chaining1223 for (i = 0; i < j; i++) {
1192 return Raven;1224 callback.call(null, i, obj[i]);
1193 },1225 }
1194
1195 /*
1196 * Installs a global window.onerror error handler
1197 * to capture and report uncaught exceptions.
1198 * At this point, install() is required to be called due
1199 * to the way TraceKit is set up.
1200 *
1201 * @return {Raven}
1202 */
1203 install: function() {
1204 if (isSetup() && !isRavenInstalled) {
1205 TraceKit.report.subscribe(handleStackInfo);
1206
1207 // Install all of the plugins
1208 each(plugins, function(_, plugin) {
1209 plugin();
1210 });
1211
1212 isRavenInstalled = true;
1213 }1226 }
1227 }
1228}
1214 1229
1215 return Raven;1230function objectMerge(obj1, obj2) {
1216 },1231 if (!obj2) {
1232 return obj1;
1233 }
1234 each(obj2, function(key, value){
1235 obj1[key] = value;
1236 });
1237 return obj1;
1238}
1217 1239
1218 /*1240function truncate(str, max) {
1219 * Wrap code within a context so Raven can capture errors1241 return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
1220 * reliably across domains that is executed immediately.1242}
1221 *
1222 * @param {object} options A specific set of options for this context [optional]
1223 * @param {function} func The callback to be immediately executed within the context
1224 * @param {array} args An array of arguments to be called with the callback [optional]
1225 */
1226 context: function(options, func, args) {
1227 if (isFunction(options)) {
1228 args = func || [];
1229 func = options;
1230 options = undefined;
1231 }
1232 1243
1233 return Raven.wrap(options, func).apply(this, args);1244/**
1234 },1245 * hasKey, a better form of hasOwnProperty
1246 * Example: hasKey(MainHostObject, property) === true/false
1247 *
1248 * @param {Object} host object to check property
1249 * @param {string} key to check
1250 */
1251function hasKey(object, key) {
1252 return objectPrototype.hasOwnProperty.call(object, key);
1253}
1235 1254
1236 /*1255function joinRegExp(patterns) {
1237 * Wrap code within a context and returns back a new function to be executed1256 // Combine an array of regular expressions and strings into one large regexp
1238 *1257 // Be mad.
1239 * @param {object} options A specific set of options for this context [optional]1258 var sources = [],
1240 * @param {function} func The function to be wrapped in a new context1259 i = 0, len = patterns.length,
1241 * @return {function} The newly wrapped functions with a context1260 pattern;
1242 */
1243 wrap: function(options, func) {
1244 // 1 argument has been passed, and it's not a function
1245 // so just return it
1246 if (isUndefined(func) && !isFunction(options)) {
1247 return options;
1248 }
1249 1261
1250 // options is optional1262 for (; i < len; i++) {
1251 if (isFunction(options)) {1263 pattern = patterns[i];
1252 func = options;1264 if (isString(pattern)) {
1253 options = undefined;1265 // If it's a string, we need to escape it
1266 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
1267 sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
1268 } else if (pattern && pattern.source) {
1269 // If it's a regexp already, we want to extract the source
1270 sources.push(pattern.source);
1254 }1271 }
1272 // Intentionally skip other cases
1273 }
1274 return new RegExp(sources.join('|'), 'i');
1275}
1255 1276
1256 // At this point, we've passed along 2 arguments, and the second one1277function urlencode(o) {
1257 // is not a function either, so we'll just return the second argument.1278 var pairs = [];
1258 if (!isFunction(func)) {1279 each(o, function(key, value) {
1259 return func;1280 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
1260 }1281 });
1282 return pairs.join('&');
1283}
1261 1284
1262 // We don't wanna wrap it twice!1285function uuid4() {
1263 if (func.__raven__) {1286 var crypto = window.crypto || window.msCrypto;
1264 return func;
1265 }
1266 1287
1267 function wrapped() {1288 if (!isUndefined(crypto) && crypto.getRandomValues) {
1268 var args = [], i = arguments.length,1289 // Use window.crypto API if available
1269 deep = !options || options && options.deep !== false;1290 var arr = new Uint16Array(8);
1270 // Recursively wrap all of a function's arguments that are1291 crypto.getRandomValues(arr);
1271 // functions themselves.
1272 1292
1273 while(i--) args[i] = deep ? Raven.wrap(options, arguments[i]) : arguments[i];1293 // set 4 in byte 7
1294 arr[3] = arr[3] & 0xFFF | 0x4000;
1295 // set 2 most significant bits of byte 9 to '10'
1296 arr[4] = arr[4] & 0x3FFF | 0x8000;
1274 1297
1275 try {1298 var pad = function(num) {
1276 /*jshint -W040*/1299 var v = num.toString(16);
1277 return func.apply(this, args);1300 while (v.length < 4) {
1278 } catch(e) {1301 v = '0' + v;
1279 Raven.captureException(e, options);
1280 throw e;
1281 }1302 }
1282 }1303 return v;
1304 };
1283 1305
1284 // copy over properties of the old function1306 return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) +
1285 for (var property in func) {1307 pad(arr[5]) + pad(arr[6]) + pad(arr[7]);
1286 if (hasKey(func, property)) {1308 } else {
1287 wrapped[property] = func[property];1309 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
1288 }1310 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1289 }1311 var r = Math.random()*16|0,
1290 wrapped.prototype = func.prototype;1312 v = c === 'x' ? r : r&0x3|0x8;
1313 return v.toString(16);
1314 });
1315 }
1316}
1291 1317
1292 // Signal that this function has been wrapped already1318module.exports = {
1293 // for both debugging and to prevent it to being wrapped twice1319 isUndefined: isUndefined,
1294 wrapped.__raven__ = true;1320 isFunction: isFunction,
1295 wrapped.__inner__ = func;1321 isString: isString,
1322 isObject: isObject,
1323 isEmptyObject: isEmptyObject,
1324 isError: isError,
1325 each: each,
1326 objectMerge: objectMerge,
1327 truncate: truncate,
1328 hasKey: hasKey,
1329 joinRegExp: joinRegExp,
1330 urlencode: urlencode,
1331 uuid4: uuid4
1332};
1296 1333
1297 return wrapped;1334},{}],5:[function(_dereq_,module,exports){
1298 },1335'use strict';
1299 1336
1300 /*1337var utils = _dereq_(4);
1301 * Uninstalls the global error handler.
1302 *
1303 * @return {Raven}
1304 */
1305 uninstall: function() {
1306 TraceKit.report.uninstall();
1307 isRavenInstalled = false;
1308 1338
1309 return Raven;1339var hasKey = utils.hasKey;
1310 },1340var isString = utils.isString;
1341var isUndefined = utils.isUndefined;
1311 1342
1312 /*1343/*
1313 * Manually capture an exception and send it over to Sentry1344 TraceKit - Cross brower stack traces - github.com/occ/TraceKit
1314 *1345 MIT license
1315 * @param {error} ex An exception to be logged1346*/
1316 * @param {object} options A specific set of options for this error [optional]1347
1317 * @return {Raven}1348var TraceKit = {
1349 remoteFetching: false,
1350 collectWindowErrors: true,
1351 // 3 lines before, the offending line, 3 lines after
1352 linesOfContext: 7,
1353 debug: false
1354};
1355
1356// global reference to slice
1357var _slice = [].slice;
1358var UNKNOWN_FUNCTION = '?';
1359
1360// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
1361var ERROR_TYPES_RE = /^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/;
1362
1363function getLocationHref() {
1364 if (typeof document === 'undefined')
1365 return '';
1366
1367 return document.location.href;
1368}
1369
1370/**
1371 * TraceKit.report: cross-browser processing of unhandled exceptions
1372 *
1373 * Syntax:
1374 * TraceKit.report.subscribe(function(stackInfo) { ... })
1375 * TraceKit.report.unsubscribe(function(stackInfo) { ... })
1376 * TraceKit.report(exception)
1377 * try { ...code... } catch(ex) { TraceKit.report(ex); }
1378 *
1379 * Supports:
1380 * - Firefox: full stack trace with line numbers, plus column number
1381 * on top frame; column number is not guaranteed
1382 * - Opera: full stack trace with line and column numbers
1383 * - Chrome: full stack trace with line and column numbers
1384 * - Safari: line and column number for the top frame only; some frames
1385 * may be missing, and column number is not guaranteed
1386 * - IE: line and column number for the top frame only; some frames
1387 * may be missing, and column number is not guaranteed
1388 *
1389 * In theory, TraceKit should work on all of the following versions:
1390 * - IE5.5+ (only 8.0 tested)
1391 * - Firefox 0.9+ (only 3.5+ tested)
1392 * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
1393 * Exceptions Have Stacktrace to be enabled in opera:config)
1394 * - Safari 3+ (only 4+ tested)
1395 * - Chrome 1+ (only 5+ tested)
1396 * - Konqueror 3.5+ (untested)
1397 *
1398 * Requires TraceKit.computeStackTrace.
1399 *
1400 * Tries to catch all unhandled exceptions and report them to the
1401 * subscribed handlers. Please note that TraceKit.report will rethrow the
1402 * exception. This is REQUIRED in order to get a useful stack trace in IE.
1403 * If the exception does not reach the top of the browser, you will only
1404 * get a stack trace from the point where TraceKit.report was called.
1405 *
1406 * Handlers receive a stackInfo object as described in the
1407 * TraceKit.computeStackTrace docs.
1408 */
1409TraceKit.report = (function reportModuleWrapper() {
1410 var handlers = [],
1411 lastArgs = null,
1412 lastException = null,
1413 lastExceptionStack = null;
1414
1415 /**
1416 * Add a crash handler.
1417 * @param {Function} handler
1318 */1418 */
1319 captureException: function(ex, options) {1419 function subscribe(handler) {
1320 // If not an Error is passed through, recall as a message instead1420 installGlobalHandler();
1321 if (!isError(ex)) return Raven.captureMessage(ex, options);1421 handlers.push(handler);
1422 }
1322 1423
1323 // Store the raw exception object for potential debugging and introspection1424 /**
1324 lastCapturedException = ex;1425 * Remove a crash handler.
1426 * @param {Function} handler
1427 */
1428 function unsubscribe(handler) {
1429 for (var i = handlers.length - 1; i >= 0; --i) {
1430 if (handlers[i] === handler) {
1431 handlers.splice(i, 1);
1432 }
1433 }
1434 }
1325 1435
1326 // TraceKit.report will re-raise any exception passed to it,1436 /**
1327 // which means you have to wrap it in try/catch. Instead, we1437 * Remove all crash handlers.
1328 // can wrap it here and only re-raise if TraceKit.report1438 */
1329 // raises an exception different from the one we asked to1439 function unsubscribeAll() {
1330 // report on.1440 uninstallGlobalHandler();
1331 try {1441 handlers = [];
1332 var stack = TraceKit.computeStackTrace(ex);1442 }
1333 handleStackInfo(stack, options);1443
1334 } catch(ex1) {1444 /**
1335 if(ex !== ex1) {1445 * Dispatch stack information to all handlers.
1336 throw ex1;1446 * @param {Object.<string, *>} stack
1447 */
1448 function notifyHandlers(stack, isWindowError) {
1449 var exception = null;
1450 if (isWindowError && !TraceKit.collectWindowErrors) {
1451 return;
1452 }
1453 for (var i in handlers) {
1454 if (hasKey(handlers, i)) {
1455 try {
1456 handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
1457 } catch (inner) {
1458 exception = inner;
1459 }
1337 }1460 }
1338 }1461 }
1339 1462
1340 return Raven;1463 if (exception) {
1341 },1464 throw exception;
1465 }
1466 }
1342 1467
1343 /*1468 var _oldOnerrorHandler, _onErrorHandlerInstalled;
1344 * Manually send a message to Sentry1469
1345 *1470 /**
1346 * @param {string} msg A plain message to be captured in Sentry1471 * Ensures all global unhandled exceptions are recorded.
1347 * @param {object} options A specific set of options for this message [optional]1472 * Supported by Gecko and IE.
1348 * @return {Raven}1473 * @param {string} message Error message.
1474 * @param {string} url URL of script that generated the exception.
1475 * @param {(number|string)} lineNo The line number at which the error
1476 * occurred.
1477 * @param {?(number|string)} colNo The column number at which the error
1478 * occurred.
1479 * @param {?Error} ex The actual Error object.
1349 */1480 */
1350 captureMessage: function(msg, options) {1481 function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
1351 // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an1482 var stack = null;
1352 // early call; we'll error on the side of logging anything called before configuration since it's1483
1353 // probably something you should see:1484 if (lastExceptionStack) {
1354 if (!!globalOptions.ignoreErrors.test && globalOptions.ignoreErrors.test(msg)) {1485 TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
1355 return;1486 processLastException();
1487 } else if (ex) {
1488 // New chrome and blink send along a real error object
1489 // Let's just report that like a normal error.
1490 // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
1491 stack = TraceKit.computeStackTrace(ex);
1492 notifyHandlers(stack, true);
1493 } else {
1494 var location = {
1495 'url': url,
1496 'line': lineNo,
1497 'column': colNo
1498 };
1499 location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
1500 location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
1501
1502 var name = undefined;
1503 var msg = message; // must be new var or will modify original `arguments`
1504 var groups;
1505 if (isString(message)) {
1506 var groups = message.match(ERROR_TYPES_RE);
1507 if (groups) {
1508 name = groups[1];
1509 msg = groups[2];
1510 }
1511 }
1512
1513 stack = {
1514 'name': name,
1515 'message': msg,
1516 'url': getLocationHref(),
1517 'stack': [location]
1518 };
1519 notifyHandlers(stack, true);
1356 }1520 }
1357 1521
1358 // Fire away!1522 if (_oldOnerrorHandler) {
1359 send(1523 return _oldOnerrorHandler.apply(this, arguments);
1360 objectMerge({1524 }
1361 message: msg + '' // Make sure it's actually a string
1362 }, options)
1363 );
1364 1525
1365 return Raven;1526 return false;
1366 },1527 }
1367 1528
1368 addPlugin: function(plugin) {1529 function installGlobalHandler ()
1369 plugins.push(plugin);1530 {
1370 if (isRavenInstalled) plugin();1531 if (_onErrorHandlerInstalled) {
1371 return Raven;1532 return;
1372 },1533 }
1534 _oldOnerrorHandler = window.onerror;
1535 window.onerror = traceKitWindowOnError;
1536 _onErrorHandlerInstalled = true;
1537 }
1373 1538
1374 /*1539 function uninstallGlobalHandler ()
1375 * Set/clear a user to be sent along with the payload.1540 {
1376 *1541 if (!_onErrorHandlerInstalled) {
1377 * @param {object} user An object representing user data [optional]1542 return;
1378 * @return {Raven}1543 }
1379 */1544 window.onerror = _oldOnerrorHandler;
1380 setUserContext: function(user) {1545 _onErrorHandlerInstalled = false;
1381 // Intentionally do not merge here since that's an unexpected behavior.1546 _oldOnerrorHandler = undefined;
1382 globalContext.user = user;1547 }
1383 1548
1384 return Raven;1549 function processLastException() {
1385 },1550 var _lastExceptionStack = lastExceptionStack,
1551 _lastArgs = lastArgs;
1552 lastArgs = null;
1553 lastExceptionStack = null;
1554 lastException = null;
1555 notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
1556 }
1386 1557
1387 /*1558 /**
1388 * Merge extra attributes to be sent along with the payload.1559 * Reports an unhandled Error to TraceKit.
1389 *1560 * @param {Error} ex
1390 * @param {object} extra An object representing extra data [optional]1561 * @param {?boolean} rethrow If false, do not re-throw the exception.
1391 * @return {Raven}1562 * Only used for window.onerror to not cause an infinite loop of
1563 * rethrowing.
1392 */1564 */
1393 setExtraContext: function(extra) {1565 function report(ex, rethrow) {
1394 mergeContext('extra', extra);1566 var args = _slice.call(arguments, 1);
1567 if (lastExceptionStack) {
1568 if (lastException === ex) {
1569 return; // already caught by an inner catch block, ignore
1570 } else {
1571 processLastException();
1572 }
1573 }
1395 1574
1396 return Raven;1575 var stack = TraceKit.computeStackTrace(ex);
1397 },1576 lastExceptionStack = stack;
1577 lastException = ex;
1578 lastArgs = args;
1398 1579
1399 /*1580 // If the stack trace is incomplete, wait for 2 seconds for
1400 * Merge tags to be sent along with the payload.1581 // slow slow IE to see if onerror occurs or not before reporting
1401 *1582 // this exception; otherwise, we will end up with an incomplete
1402 * @param {object} tags An object representing tags [optional]1583 // stack trace
1403 * @return {Raven}1584 window.setTimeout(function () {
1585 if (lastException === ex) {
1586 processLastException();
1587 }
1588 }, (stack.incomplete ? 2000 : 0));
1589
1590 if (rethrow !== false) {
1591 throw ex; // re-throw to propagate to the top level (and cause window.onerror)
1592 }
1593 }
1594
1595 report.subscribe = subscribe;
1596 report.unsubscribe = unsubscribe;
1597 report.uninstall = unsubscribeAll;
1598 return report;
1599}());
1600
1601/**
1602 * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
1603 *
1604 * Syntax:
1605 * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
1606 * Returns:
1607 * s.name - exception name
1608 * s.message - exception message
1609 * s.stack[i].url - JavaScript or HTML file URL
1610 * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
1611 * s.stack[i].args - arguments passed to the function, if known
1612 * s.stack[i].line - line number, if known
1613 * s.stack[i].column - column number, if known
1614 * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
1615 *
1616 * Supports:
1617 * - Firefox: full stack trace with line numbers and unreliable column
1618 * number on top frame
1619 * - Opera 10: full stack trace with line and column numbers
1620 * - Opera 9-: full stack trace with line numbers
1621 * - Chrome: full stack trace with line and column numbers
1622 * - Safari: line and column number for the topmost stacktrace element
1623 * only
1624 * - IE: no line numbers whatsoever
1625 *
1626 * Tries to guess names of anonymous functions by looking for assignments
1627 * in the source code. In IE and Safari, we have to guess source file names
1628 * by searching for function bodies inside all page scripts. This will not
1629 * work for scripts that are loaded cross-domain.
1630 * Here be dragons: some function names may be guessed incorrectly, and
1631 * duplicate functions may be mismatched.
1632 *
1633 * TraceKit.computeStackTrace should only be used for tracing purposes.
1634 * Logging of unhandled exceptions should be done with TraceKit.report,
1635 * which builds on top of TraceKit.computeStackTrace and provides better
1636 * IE support by utilizing the window.onerror event to retrieve information
1637 * about the top of the stack.
1638 *
1639 * Note: In IE and Safari, no stack trace is recorded on the Error object,
1640 * so computeStackTrace instead walks its *own* chain of callers.
1641 * This means that:
1642 * * in Safari, some methods may be missing from the stack trace;
1643 * * in IE, the topmost function in the stack trace will always be the
1644 * caller of computeStackTrace.
1645 *
1646 * This is okay for tracing (because you are likely to be calling
1647 * computeStackTrace from the function you want to be the topmost element
1648 * of the stack trace anyway), but not okay for logging unhandled
1649 * exceptions (because your catch block will likely be far away from the
1650 * inner function that actually caused the exception).
1651 *
1652 */
1653TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
1654 var sourceCache = {};
1655
1656 /**
1657 * Attempts to retrieve source code via XMLHttpRequest, which is used
1658 * to look up anonymous function names.
1659 * @param {string} url URL of source code.
1660 * @return {string} Source contents.
1404 */1661 */
1405 setTagsContext: function(tags) {1662 function loadSource(url) {
1406 mergeContext('tags', tags);1663 if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
1664 return '';
1665 }
1666 try {
1667 var getXHR = function() {
1668 try {
1669 return new window.XMLHttpRequest();
1670 } catch (e) {
1671 // explicitly bubble up the exception if not found
1672 return new window.ActiveXObject('Microsoft.XMLHTTP');
1673 }
1674 };
1407 1675
1408 return Raven;1676 var request = getXHR();
1409 },1677 request.open('GET', url, false);
1678 request.send('');
1679 return request.responseText;
1680 } catch (e) {
1681 return '';
1682 }
1683 }
1410 1684
1411 /*1685 /**
1412 * Clear all of the context.1686 * Retrieves source code from the source code cache.
1413 *1687 * @param {string} url URL of source code.
1414 * @return {Raven}1688 * @return {Array.<string>} Source contents.
1415 */1689 */
1416 clearContext: function() {1690 function getSource(url) {
1417 globalContext = {};1691 if (!isString(url)) return [];
1692 if (!hasKey(sourceCache, url)) {
1693 // URL needs to be able to fetched within the acceptable domain. Otherwise,
1694 // cross-domain errors will be triggered.
1695 var source = '';
1696 var domain = '';
1697 try { domain = document.domain; } catch (e) {}
1698 if (url.indexOf(domain) !== -1) {
1699 source = loadSource(url);
1700 }
1701 sourceCache[url] = source ? source.split('\n') : [];
1702 }
1418 1703
1419 return Raven;1704 return sourceCache[url];
1420 },1705 }
1421 1706
1422 /*1707 /**
1423 * Get a copy of the current context. This cannot be mutated.1708 * Tries to use an externally loaded copy of source code to determine
1424 *1709 * the name of a function by looking at the name of the variable it was
1425 * @return {object} copy of context1710 * assigned to, if any.
1711 * @param {string} url URL of source code.
1712 * @param {(string|number)} lineNo Line number in source code.
1713 * @return {string} The function name, if discoverable.
1426 */1714 */
1427 getContext: function() {1715 function guessFunctionName(url, lineNo) {
1428 // lol javascript1716 var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
1429 return JSON.parse(JSON.stringify(globalContext));1717 reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
1430 },1718 line = '',
1719 maxLines = 10,
1720 source = getSource(url),
1721 m;
1431 1722
1432 /*1723 if (!source.length) {
1433 * Set release version of application1724 return UNKNOWN_FUNCTION;
1434 *1725 }
1435 * @param {string} release Typically something like a git SHA to identify version1726
1436 * @return {Raven}1727 // Walk backwards from the first line in the function until we find the line which
1728 // matches the pattern above, which is the function definition
1729 for (var i = 0; i < maxLines; ++i) {
1730 line = source[lineNo - i] + line;
1731
1732 if (!isUndefined(line)) {
1733 if ((m = reGuessFunction.exec(line))) {
1734 return m[1];
1735 } else if ((m = reFunctionArgNames.exec(line))) {
1736 return m[1];
1737 }
1738 }
1739 }
1740
1741 return UNKNOWN_FUNCTION;
1742 }
1743
1744 /**
1745 * Retrieves the surrounding lines from where an exception occurred.
1746 * @param {string} url URL of source code.
1747 * @param {(string|number)} line Line number in source code to centre
1748 * around for context.
1749 * @return {?Array.<string>} Lines of source code.
1437 */1750 */
1438 setRelease: function(release) {1751 function gatherContext(url, line) {
1439 globalOptions.release = release;1752 var source = getSource(url);
1440 1753
1441 return Raven;1754 if (!source.length) {
1442 },1755 return null;
1756 }
1443 1757
1444 /*1758 var context = [],
1445 * Set the dataCallback option1759 // linesBefore & linesAfter are inclusive with the offending line.
1446 *1760 // if linesOfContext is even, there will be one extra line
1447 * @param {function} callback The callback to run which allows the1761 // *before* the offending line.
1448 * data blob to be mutated before sending1762 linesBefore = Math.floor(TraceKit.linesOfContext / 2),
1449 * @return {Raven}1763 // Add one extra line if linesOfContext is odd
1450 */1764 linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
1451 setDataCallback: function(callback) {1765 start = Math.max(0, line - linesBefore - 1),
1452 globalOptions.dataCallback = callback;1766 end = Math.min(source.length, line + linesAfter - 1);
1453 1767
1454 return Raven;1768 line -= 1; // convert to 0-based index
1455 },
1456 1769
1457 /*1770 for (var i = start; i < end; ++i) {
1458 * Set the shouldSendCallback option1771 if (!isUndefined(source[i])) {
1459 *1772 context.push(source[i]);
1460 * @param {function} callback The callback to run which allows1773 }
1461 * introspecting the blob before sending1774 }
1462 * @return {Raven}
1463 */
1464 setShouldSendCallback: function(callback) {
1465 globalOptions.shouldSendCallback = callback;
1466 1775
1467 return Raven;1776 return context.length > 0 ? context : null;
1468 },1777 }
1469 1778
1470 /**1779 /**
1471 * Override the default HTTP transport mechanism that transmits data1780 * Escapes special characters, except for whitespace, in a string to be
1472 * to the Sentry server.1781 * used inside a regular expression as a string literal.
1473 *1782 * @param {string} text The string.
1474 * @param {function} transport Function invoked instead of the default1783 * @return {string} The escaped string literal.
1475 * `makeRequest` handler.
1476 *
1477 * @return {Raven}
1478 */1784 */
1479 setTransport: function(transport) {1785 function escapeRegExp(text) {
1480 globalOptions.transport = transport;1786 return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
1481 1787 }
1482 return Raven;
1483 },
1484 1788
1485 /*1789 /**
1486 * Get the latest raw exception that was captured by Raven.1790 * Escapes special characters in a string to be used inside a regular
1487 *1791 * expression as a string literal. Also ensures that HTML entities will
1488 * @return {error}1792 * be matched the same as their literal friends.
1793 * @param {string} body The string.
1794 * @return {string} The escaped string.
1489 */1795 */
1490 lastException: function() {1796 function escapeCodeAsRegExpForMatchingInsideHTML(body) {
1491 return lastCapturedException;1797 return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
1492 },1798 }
1493 1799
1494 /*1800 /**
1495 * Get the last event id1801 * Determines where a code fragment occurs in the source code.
1496 *1802 * @param {RegExp} re The function definition.
1497 * @return {string}1803 * @param {Array.<string>} urls A list of URLs to search.
1804 * @return {?Object.<string, (string|number)>} An object containing
1805 * the url, line, and column number of the defined function.
1498 */1806 */
1499 lastEventId: function() {1807 function findSourceInUrls(re, urls) {
1500 return lastEventId;1808 var source, m;
1501 },1809 for (var i = 0, j = urls.length; i < j; ++i) {
1810 // console.log('searching', urls[i]);
1811 if ((source = getSource(urls[i])).length) {
1812 source = source.join('\n');
1813 if ((m = re.exec(source))) {
1814 // console.log('Found function in ' + urls[i]);
1502 1815
1503 /*1816 return {
1504 * Determine if Raven is setup and ready to go.1817 'url': urls[i],
1505 *1818 'line': source.substring(0, m.index).split('\n').length,
1506 * @return {boolean}1819 'column': m.index - source.lastIndexOf('\n', m.index) - 1
1507 */1820 };
1508 isSetup: function() {1821 }
1509 return isSetup();1822 }
1510 }1823 }
1511};
1512 1824
1513// Deprecations1825 // console.log('no match');
1514Raven.setUser = Raven.setUserContext;
1515Raven.setReleaseContext = Raven.setRelease;
1516 1826
1517function triggerEvent(eventType, options) {1827 return null;
1518 // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it1828 }
1519 var evt, key;
1520 1829
1521 if (!hasDocument)1830 /**
1522 return;1831 * Determines at which column a code fragment occurs on a line of the
1832 * source code.
1833 * @param {string} fragment The code fragment.
1834 * @param {string} url The URL to search.
1835 * @param {(string|number)} line The line number to examine.
1836 * @return {?number} The column number.
1837 */
1838 function findSourceInLine(fragment, url, line) {
1839 var source = getSource(url),
1840 re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
1841 m;
1523 1842
1524 options = options || {};1843 line -= 1;
1525 1844
1526 eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1);1845 if (source && source.length > line && (m = re.exec(source[line]))) {
1846 return m.index;
1847 }
1527 1848
1528 if (document.createEvent) {1849 return null;
1529 evt = document.createEvent('HTMLEvents');
1530 evt.initEvent(eventType, true, true);
1531 } else {
1532 evt = document.createEventObject();
1533 evt.eventType = eventType;
1534 }1850 }
1535 1851
1536 for (key in options) if (hasKey(options, key)) {1852 /**
1537 evt[key] = options[key];1853 * Determines where a function was defined within the source code.
1538 }1854 * @param {(Function|string)} func A function reference or serialized
1855 * function definition.
1856 * @return {?Object.<string, (string|number)>} An object containing
1857 * the url, line, and column number of the defined function.
1858 */
1859 function findSourceByFunctionBody(func) {
1860 if (typeof document === 'undefined')
1861 return;
1539 1862
1540 if (document.createEvent) {1863 var urls = [window.location.href],
1541 // IE9 if standards1864 scripts = document.getElementsByTagName('script'),
1542 document.dispatchEvent(evt);1865 body,
1543 } else {1866 code = '' + func,
1544 // IE8 regardless of Quirks or Standards1867 codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
1545 // IE9 if quirks1868 eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
1546 try {1869 re,
1547 document.fireEvent('on' + evt.eventType.toLowerCase(), evt);1870 parts,
1548 } catch(e) {}1871 result;
1549 }
1550}
1551 1872
1552var dsnKeys = 'source protocol user pass host port path'.split(' '),1873 for (var i = 0; i < scripts.length; ++i) {
1553 dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;1874 var script = scripts[i];
1875 if (script.src) {
1876 urls.push(script.src);
1877 }
1878 }
1554 1879
1555function RavenConfigError(message) {1880 if (!(parts = codeRE.exec(code))) {
1556 this.name = 'RavenConfigError';1881 re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
1557 this.message = message;1882 }
1558}
1559RavenConfigError.prototype = new Error();
1560RavenConfigError.prototype.constructor = RavenConfigError;
1561 1883
1562/**** Private functions ****/1884 // not sure if this is really necessary, but I don’t have a test
1563function parseDSN(str) {1885 // corpus large enough to confirm that and it was in the original.
1564 var m = dsnPattern.exec(str),1886 else {
1565 dsn = {},1887 var name = parts[1] ? '\\s+' + parts[1] : '',
1566 i = 7;1888 args = parts[2].split(',').join('\\s*,\\s*');
1567 1889
1568 try {1890 body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
1569 while (i--) dsn[dsnKeys[i]] = m[i] || '';1891 re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
1570 } catch(e) {1892 }
1571 throw new RavenConfigError('Invalid DSN: ' + str);
1572 }
1573 1893
1574 if (dsn.pass)1894 // look for a normal function definition
1575 throw new RavenConfigError('Do not specify your private key in the DSN!');1895 if ((result = findSourceInUrls(re, urls))) {
1896 return result;
1897 }
1576 1898
1577 return dsn;1899 // look for an old-school event handler function
1578}1900 if ((parts = eventRE.exec(code))) {
1901 var event = parts[1];
1902 body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
1579 1903
1580function isUndefined(what) {1904 // look for a function defined in HTML as an onXXX handler
1581 return what === void 0;1905 re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
1582}
1583 1906
1584function isFunction(what) {1907 if ((result = findSourceInUrls(re, urls[0]))) {
1585 return typeof what === 'function';1908 return result;
1586}1909 }
1587 1910
1588function isString(what) {1911 // look for ???
1589 return objectPrototype.toString.call(what) === '[object String]';1912 re = new RegExp(body);
1590}
1591 1913
1592function isObject(what) {1914 if ((result = findSourceInUrls(re, urls))) {
1593 return typeof what === 'object' && what !== null;1915 return result;
1594}1916 }
1917 }
1595 1918
1596function isEmptyObject(what) {1919 return null;
1597 for (var k in what) return false;1920 }
1598 return true;
1599}
1600 1921
1601// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L5601922 // Contents of Exception in various browsers.
1602// with some tiny modifications1923 //
1603function isError(what) {1924 // SAFARI:
1604 return isObject(what) &&1925 // ex.message = Can't find variable: qq
1605 objectPrototype.toString.call(what) === '[object Error]' ||1926 // ex.line = 59
1606 what instanceof Error;1927 // ex.sourceId = 580238192
1607}1928 // ex.sourceURL = http://...
1929 // ex.expressionBeginOffset = 96
1930 // ex.expressionCaretOffset = 98
1931 // ex.expressionEndOffset = 98
1932 // ex.name = ReferenceError
1933 //
1934 // FIREFOX:
1935 // ex.message = qq is not defined
1936 // ex.fileName = http://...
1937 // ex.lineNumber = 59
1938 // ex.columnNumber = 69
1939 // ex.stack = ...stack trace... (see the example below)
1940 // ex.name = ReferenceError
1941 //
1942 // CHROME:
1943 // ex.message = qq is not defined
1944 // ex.name = ReferenceError
1945 // ex.type = not_defined
1946 // ex.arguments = ['aa']
1947 // ex.stack = ...stack trace...
1948 //
1949 // INTERNET EXPLORER:
1950 // ex.message = ...
1951 // ex.name = ReferenceError
1952 //
1953 // OPERA:
1954 // ex.message = ...message... (see the example below)
1955 // ex.name = ReferenceError
1956 // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
1957 // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
1608 1958
1609/**1959 /**
1610 * hasKey, a better form of hasOwnProperty1960 * Computes stack trace information from the stack property.
1611 * Example: hasKey(MainHostObject, property) === true/false1961 * Chrome and Gecko use this property.
1612 *1962 * @param {Error} ex
1613 * @param {Object} host object to check property1963 * @return {?Object.<string, *>} Stack trace information.
1614 * @param {string} key to check1964 */
1615 */1965 function computeStackTraceFromStackProp(ex) {
1616function hasKey(object, key) {1966 if (isUndefined(ex.stack) || !ex.stack) return;
1617 return objectPrototype.hasOwnProperty.call(object, key);
1618}
1619 1967
1620function each(obj, callback) {1968 var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|<anonymous>).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
1621 var i, j;1969 gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
1970 winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
1971 lines = ex.stack.split('\n'),
1972 stack = [],
1973 parts,
1974 element,
1975 reference = /^(.*) is undefined$/.exec(ex.message);
1622 1976
1623 if (isUndefined(obj.length)) {1977 for (var i = 0, j = lines.length; i < j; ++i) {
1624 for (i in obj) {1978 if ((parts = chrome.exec(lines[i]))) {
1625 if (hasKey(obj, i)) {1979 var isNative = parts[2] && parts[2].indexOf('native') !== -1;
1626 callback.call(null, i, obj[i]);1980 element = {
1627 }1981 'url': !isNative ? parts[2] : null,
1628 }1982 'func': parts[1] || UNKNOWN_FUNCTION,
1629 } else {1983 'args': isNative ? [parts[2]] : [],
1630 j = obj.length;1984 'line': parts[3] ? +parts[3] : null,
1631 if (j) {1985 'column': parts[4] ? +parts[4] : null
1632 for (i = 0; i < j; i++) {1986 };
1633 callback.call(null, i, obj[i]);1987 } else if ( parts = winjs.exec(lines[i]) ) {
1988 element = {
1989 'url': parts[2],
1990 'func': parts[1] || UNKNOWN_FUNCTION,
1991 'args': [],
1992 'line': +parts[3],
1993 'column': parts[4] ? +parts[4] : null
1994 };
1995 } else if ((parts = gecko.exec(lines[i]))) {
1996 element = {
1997 'url': parts[3],
1998 'func': parts[1] || UNKNOWN_FUNCTION,
1999 'args': parts[2] ? parts[2].split(',') : [],
2000 'line': parts[4] ? +parts[4] : null,
2001 'column': parts[5] ? +parts[5] : null
2002 };
2003 } else {
2004 continue;
1634 }2005 }
1635 }
1636 }
1637}
1638 2006
1639function handleStackInfo(stackInfo, options) {2007 if (!element.func && element.line) {
1640 var frames = [];2008 element.func = guessFunctionName(element.url, element.line);
1641
1642 if (stackInfo.stack && stackInfo.stack.length) {
1643 each(stackInfo.stack, function(i, stack) {
1644 var frame = normalizeFrame(stack);
1645 if (frame) {
1646 frames.push(frame);
1647 }2009 }
1648 });
1649 }
1650
1651 triggerEvent('handle', {
1652 stackInfo: stackInfo,
1653 options: options
1654 });
1655
1656 processException(
1657 stackInfo.name,
1658 stackInfo.message,
1659 stackInfo.url,
1660 stackInfo.lineno,
1661 frames,
1662 options
1663 );
1664}
1665
1666function normalizeFrame(frame) {
1667 if (!frame.url) return;
1668
1669 // normalize the frames data
1670 var normalized = {
1671 filename: frame.url,
1672 lineno: frame.line,
1673 colno: frame.column,
1674 'function': frame.func || '?'
1675 }, context = extractContextFromFrame(frame), i;
1676
1677 if (context) {
1678 var keys = ['pre_context', 'context_line', 'post_context'];
1679 i = 3;
1680 while (i--) normalized[keys[i]] = context[i];
1681 }
1682
1683 normalized.in_app = !( // determine if an exception came from outside of our app
1684 // first we check the global includePaths list.
1685 (!!globalOptions.includePaths.test && !globalOptions.includePaths.test(normalized.filename)) ||
1686 // Now we check for fun, if the function name is Raven or TraceKit
1687 /(Raven|TraceKit)\./.test(normalized['function']) ||
1688 // finally, we do a last ditch effort and check for raven.min.js
1689 /raven\.(min\.)?js$/.test(normalized.filename)
1690 );
1691 2010
1692 return normalized;2011 if (element.line) {
1693}2012 element.context = gatherContext(element.url, element.line);
1694 2013 }
1695function extractContextFromFrame(frame) {
1696 // immediately check if we should even attempt to parse a context
1697 if (!frame.context || !globalOptions.fetchContext) return;
1698
1699 var context = frame.context,
1700 pivot = ~~(context.length / 2),
1701 i = context.length, isMinified = false;
1702 2014
1703 while (i--) {2015 stack.push(element);
1704 // We're making a guess to see if the source is minified or not.
1705 // To do that, we make the assumption if *any* of the lines passed
1706 // in are greater than 300 characters long, we bail.
1707 // Sentry will see that there isn't a context
1708 if (context[i].length > 300) {
1709 isMinified = true;
1710 break;
1711 }2016 }
1712 }
1713 2017
1714 if (isMinified) {2018 if (!stack.length) {
1715 // The source is minified and we don't know which column. Fuck it.2019 return null;
1716 if (isUndefined(frame.column)) return;2020 }
1717
1718 // If the source is minified and has a frame column
1719 // we take a chunk of the offending line to hopefully shed some light
1720 return [
1721 [], // no pre_context
1722 context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column
1723 [] // no post_context
1724 ];
1725 }
1726 2021
1727 return [2022 if (stack[0].line && !stack[0].column && reference) {
1728 context.slice(0, pivot), // pre_context2023 stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
1729 context[pivot], // context_line2024 } else if (!stack[0].column && !isUndefined(ex.columnNumber)) {
1730 context.slice(pivot + 1) // post_context2025 // FireFox uses this awesome columnNumber property for its top frame
1731 ];2026 // Also note, Firefox's column number is 0-based and everything else expects 1-based,
1732}2027 // so adding 1
2028 stack[0].column = ex.columnNumber + 1;
2029 }
1733 2030
1734function processException(type, message, fileurl, lineno, frames, options) {2031 return {
1735 var stacktrace, i, fullMessage;2032 'name': ex.name,
1736 2033 'message': ex.message,
1737 if (!!globalOptions.ignoreErrors.test && globalOptions.ignoreErrors.test(message)) return;2034 'url': getLocationHref(),
1738 2035 'stack': stack
1739 message += '';
1740 fullMessage = type + ': ' + message;
1741
1742 if (frames && frames.length) {
1743 fileurl = frames[0].filename || fileurl;
1744 // Sentry expects frames oldest to newest
1745 // and JS sends them as newest to oldest
1746 frames.reverse();
1747 stacktrace = {frames: frames};
1748 } else if (fileurl) {
1749 stacktrace = {
1750 frames: [{
1751 filename: fileurl,
1752 lineno: lineno,
1753 in_app: true
1754 }]
1755 };2036 };
1756 }2037 }
1757 2038
1758 if (!!globalOptions.ignoreUrls.test && globalOptions.ignoreUrls.test(fileurl)) return;2039 /**
1759 if (!!globalOptions.whitelistUrls.test && !globalOptions.whitelistUrls.test(fileurl)) return;2040 * Computes stack trace information from the stacktrace property.
2041 * Opera 10 uses this property.
2042 * @param {Error} ex
2043 * @return {?Object.<string, *>} Stack trace information.
2044 */
2045 function computeStackTraceFromStacktraceProp(ex) {
2046 // Access and store the stacktrace property before doing ANYTHING
2047 // else to it because Opera is not very good at providing it
2048 // reliably in other circumstances.
2049 var stacktrace = ex.stacktrace;
2050 if (isUndefined(ex.stacktrace) || !ex.stacktrace) return;
1760 2051
1761 // Fire away!2052 var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,
1762 send(2053 opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,
1763 objectMerge({2054 lines = stacktrace.split('\n'),
1764 // sentry.interfaces.Exception2055 stack = [],
1765 exception: {2056 parts;
1766 values: [{
1767 type: type,
1768 value: message,
1769 stacktrace: stacktrace
1770 }]
1771 },
1772 culprit: fileurl,
1773 message: fullMessage
1774 }, options)
1775 );
1776}
1777 2057
1778function objectMerge(obj1, obj2) {2058 for (var line = 0; line < lines.length; line += 2) {
1779 if (!obj2) {2059 var element = null;
1780 return obj1;2060 if ((parts = opera10Regex.exec(lines[line]))) {
1781 }2061 element = {
1782 each(obj2, function(key, value){2062 'url': parts[2],
1783 obj1[key] = value;2063 'line': +parts[1],
1784 });2064 'column': null,
1785 return obj1;2065 'func': parts[3],
1786}2066 'args':[]
2067 };
2068 } else if ((parts = opera11Regex.exec(lines[line]))) {
2069 element = {
2070 'url': parts[6],
2071 'line': +parts[1],
2072 'column': +parts[2],
2073 'func': parts[3] || parts[4],
2074 'args': parts[5] ? parts[5].split(',') : []
2075 };
2076 }
1787 2077
1788function truncate(str, max) {2078 if (element) {
1789 return str.length <= max ? str : str.substr(0, max) + '\u2026';2079 if (!element.func && element.line) {
1790}2080 element.func = guessFunctionName(element.url, element.line);
2081 }
2082 if (element.line) {
2083 try {
2084 element.context = gatherContext(element.url, element.line);
2085 } catch (exc) {}
2086 }
1791 2087
1792function trimPacket(data) {2088 if (!element.context) {
1793 // For now, we only want to truncate the two different messages2089 element.context = [lines[line + 1]];
1794 // but this could/should be expanded to just trim everything2090 }
1795 var max = globalOptions.maxMessageLength;
1796 data.message = truncate(data.message, max);
1797 if (data.exception) {
1798 var exception = data.exception.values[0];
1799 exception.value = truncate(exception.value, max);
1800 }
1801 2091
1802 return data;2092 stack.push(element);
1803}2093 }
2094 }
1804 2095
1805function now() {2096 if (!stack.length) {
1806 return +new Date();2097 return null;
1807}2098 }
1808 2099
1809function getHttpData() {2100 return {
1810 if (!hasDocument || !document.location || !document.location.href) {2101 'name': ex.name,
1811 return;2102 'message': ex.message,
2103 'url': getLocationHref(),
2104 'stack': stack
2105 };
1812 }2106 }
1813 2107
1814 var httpData = {2108 /**
1815 headers: {2109 * NOT TESTED.
1816 'User-Agent': navigator.userAgent2110 * Computes stack trace information from an error message that includes
2111 * the stack trace.
2112 * Opera 9 and earlier use this method if the option to show stack
2113 * traces is turned on in opera:config.
2114 * @param {Error} ex
2115 * @return {?Object.<string, *>} Stack information.
2116 */
2117 function computeStackTraceFromOperaMultiLineMessage(ex) {
2118 // Opera includes a stack trace into the exception message. An example is:
2119 //
2120 // Statement on line 3: Undefined variable: undefinedFunc
2121 // Backtrace:
2122 // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
2123 // undefinedFunc(a);
2124 // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
2125 // zzz(x, y, z);
2126 // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
2127 // yyy(a, a, a);
2128 // Line 1 of function script
2129 // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
2130 // ...
2131
2132 var lines = ex.message.split('\n');
2133 if (lines.length < 4) {
2134 return null;
1817 }2135 }
1818 };
1819 2136
1820 httpData.url = document.location.href;2137 var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
2138 lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
2139 lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
2140 stack = [],
2141 scripts = document.getElementsByTagName('script'),
2142 inlineScriptBlocks = [],
2143 parts;
1821 2144
1822 if (document.referrer) {2145 for (var s in scripts) {
1823 httpData.headers.Referer = document.referrer;2146 if (hasKey(scripts, s) && !scripts[s].src) {
1824 }2147 inlineScriptBlocks.push(scripts[s]);
2148 }
2149 }
1825 2150
1826 return httpData;2151 for (var line = 2; line < lines.length; line += 2) {
1827}2152 var item = null;
2153 if ((parts = lineRE1.exec(lines[line]))) {
2154 item = {
2155 'url': parts[2],
2156 'func': parts[3],
2157 'args': [],
2158 'line': +parts[1],
2159 'column': null
2160 };
2161 } else if ((parts = lineRE2.exec(lines[line]))) {
2162 item = {
2163 'url': parts[3],
2164 'func': parts[4],
2165 'args': [],
2166 'line': +parts[1],
2167 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
2168 };
2169 var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
2170 var script = inlineScriptBlocks[parts[2] - 1];
2171 if (script) {
2172 var source = getSource(item.url);
2173 if (source) {
2174 source = source.join('\n');
2175 var pos = source.indexOf(script.innerText);
2176 if (pos >= 0) {
2177 item.line = relativeLine + source.substring(0, pos).split('\n').length;
2178 }
2179 }
2180 }
2181 } else if ((parts = lineRE3.exec(lines[line]))) {
2182 var url = window.location.href.replace(/#.*$/, '');
2183 var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[line + 1]));
2184 var src = findSourceInUrls(re, [url]);
2185 item = {
2186 'url': url,
2187 'func': '',
2188 'args': [],
2189 'line': src ? src.line : parts[1],
2190 'column': null
2191 };
2192 }
2193
2194 if (item) {
2195 if (!item.func) {
2196 item.func = guessFunctionName(item.url, item.line);
2197 }
2198 var context = gatherContext(item.url, item.line);
2199 var midline = (context ? context[Math.floor(context.length / 2)] : null);
2200 if (context && midline.replace(/^\s*/, '') === lines[line + 1].replace(/^\s*/, '')) {
2201 item.context = context;
2202 } else {
2203 // 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);
2204 item.context = [lines[line + 1]];
2205 }
2206 stack.push(item);
2207 }
2208 }
1828 2209
1829function send(data) {2210 if (!stack.length) {
1830 var baseData = {2211 return null; // could not parse multiline exception message as Opera stack trace
1831 project: globalProject,2212 }
1832 logger: globalOptions.logger,
1833 platform: 'javascript'
1834 }, httpData = getHttpData();
1835 2213
1836 if (httpData) {2214 return {
1837 baseData.request = httpData;2215 'name': ex.name,
2216 'message': lines[0],
2217 'url': getLocationHref(),
2218 'stack': stack
2219 };
1838 }2220 }
1839 2221
1840 data = objectMerge(baseData, data);2222 /**
2223 * Adds information about the first frame to incomplete stack traces.
2224 * Safari and IE require this to get complete data on the first frame.
2225 * @param {Object.<string, *>} stackInfo Stack trace information from
2226 * one of the compute* methods.
2227 * @param {string} url The URL of the script that caused an error.
2228 * @param {(number|string)} lineNo The line number of the script that
2229 * caused an error.
2230 * @param {string=} message The error generated by the browser, which
2231 * hopefully contains the name of the object that caused the error.
2232 * @return {boolean} Whether or not the stack information was
2233 * augmented.
2234 */
2235 function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
2236 var initial = {
2237 'url': url,
2238 'line': lineNo
2239 };
1841 2240
1842 // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge2241 if (initial.url && initial.line) {
1843 data.tags = objectMerge(objectMerge({}, globalContext.tags), data.tags);2242 stackInfo.incomplete = false;
1844 data.extra = objectMerge(objectMerge({}, globalContext.extra), data.extra);
1845 2243
1846 // Send along our own collected metadata with extra2244 if (!initial.func) {
1847 data.extra['session:duration'] = now() - startTime;2245 initial.func = guessFunctionName(initial.url, initial.line);
2246 }
1848 2247
1849 // If there are no tags/extra, strip the key from the payload alltogther.2248 if (!initial.context) {
1850 if (isEmptyObject(data.tags)) delete data.tags;2249 initial.context = gatherContext(initial.url, initial.line);
2250 }
1851 2251
1852 if (globalContext.user) {2252 var reference = / '([^']+)' /.exec(message);
1853 // sentry.interfaces.User2253 if (reference) {
1854 data.user = globalContext.user;2254 initial.column = findSourceInLine(reference[1], initial.url, initial.line);
1855 }2255 }
1856 2256
1857 // Include the release if it's defined in globalOptions2257 if (stackInfo.stack.length > 0) {
1858 if (globalOptions.release) data.release = globalOptions.release;2258 if (stackInfo.stack[0].url === initial.url) {
1859 // Include server_name if it's defined in globalOptions2259 if (stackInfo.stack[0].line === initial.line) {
1860 if (globalOptions.serverName) data.server_name = globalOptions.serverName;2260 return false; // already in stack trace
2261 } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
2262 stackInfo.stack[0].line = initial.line;
2263 stackInfo.stack[0].context = initial.context;
2264 return false;
2265 }
2266 }
2267 }
1861 2268
1862 if (isFunction(globalOptions.dataCallback)) {2269 stackInfo.stack.unshift(initial);
1863 data = globalOptions.dataCallback(data) || data;2270 stackInfo.partial = true;
1864 }2271 return true;
2272 } else {
2273 stackInfo.incomplete = true;
2274 }
1865 2275
1866 // Why??????????2276 return false;
1867 if (!data || isEmptyObject(data)) {
1868 return;
1869 }2277 }
1870 2278
1871 // Check if the request should be filtered or not2279 /**
1872 if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) {2280 * Computes stack trace information by walking the arguments.caller
1873 return;2281 * chain at the time the exception occurred. This will cause earlier
1874 }2282 * frames to be missed but is the only way to get any stack trace in
2283 * Safari and IE. The top frame is restored by
2284 * {@link augmentStackTraceWithInitialElement}.
2285 * @param {Error} ex
2286 * @return {?Object.<string, *>} Stack trace information.
2287 */
2288 function computeStackTraceByWalkingCallerChain(ex, depth) {
2289 var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
2290 stack = [],
2291 funcs = {},
2292 recursion = false,
2293 parts,
2294 item,
2295 source;
1875 2296
1876 // Send along an event_id if not explicitly passed.2297 for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
1877 // This event_id can be used to reference the error within Sentry itself.2298 if (curr === computeStackTrace || curr === TraceKit.report) {
1878 // Set lastEventId after we know the error should actually be sent2299 // console.log('skipping internal function');
1879 lastEventId = data.event_id || (data.event_id = uuid4());2300 continue;
2301 }
1880 2302
1881 // Try and clean up the packet before sending by truncating long values2303 item = {
1882 data = trimPacket(data);2304 'url': null,
2305 'func': UNKNOWN_FUNCTION,
2306 'line': null,
2307 'column': null
2308 };
1883 2309
1884 logDebug('debug', 'Raven about to send:', data);2310 if (curr.name) {
2311 item.func = curr.name;
2312 } else if ((parts = functionName.exec(curr.toString()))) {
2313 item.func = parts[1];
2314 }
1885 2315
1886 if (!isSetup()) return;2316 if (typeof item.func === 'undefined') {
2317 try {
2318 item.func = parts.input.substring(0, parts.input.indexOf('{'));
2319 } catch (e) { }
2320 }
1887 2321
1888 (globalOptions.transport || makeRequest)({2322 if ((source = findSourceByFunctionBody(curr))) {
1889 url: globalServer,2323 item.url = source.url;
1890 auth: {2324 item.line = source.line;
1891 sentry_version: '7',
1892 sentry_client: 'raven-js/' + Raven.VERSION,
1893 sentry_key: globalKey
1894 },
1895 data: data,
1896 options: globalOptions,
1897 onSuccess: function success() {
1898 triggerEvent('success', {
1899 data: data,
1900 src: globalServer
1901 });
1902 },
1903 onError: function failure() {
1904 triggerEvent('failure', {
1905 data: data,
1906 src: globalServer
1907 });
1908 }
1909 });
1910}
1911 2325
1912function makeRequest(opts) {2326 if (item.func === UNKNOWN_FUNCTION) {
1913 // Tack on sentry_data to auth options, which get urlencoded2327 item.func = guessFunctionName(item.url, item.line);
1914 opts.auth.sentry_data = JSON.stringify(opts.data);2328 }
1915 2329
1916 var img = newImage(),2330 var reference = / '([^']+)' /.exec(ex.message || ex.description);
1917 src = opts.url + '?' + urlencode(opts.auth),2331 if (reference) {
1918 crossOrigin = opts.options.crossOrigin;2332 item.column = findSourceInLine(reference[1], source.url, source.line);
2333 }
2334 }
1919 2335
1920 if (crossOrigin || crossOrigin === '') {2336 if (funcs['' + curr]) {
1921 img.crossOrigin = crossOrigin;2337 recursion = true;
1922 }2338 }else{
1923 img.onload = opts.onSuccess;2339 funcs['' + curr] = true;
1924 img.onerror = img.onabort = opts.onError;2340 }
1925 img.src = src;
1926}
1927 2341
1928// Note: this is shitty, but I can't figure out how to get2342 stack.push(item);
1929// sinon to stub document.createElement without breaking everything2343 }
1930// so this wrapper is just so I can stub it for tests.
1931function newImage() {
1932 return document.createElement('img');
1933}
1934 2344
1935var ravenNotConfiguredError;2345 if (depth) {
2346 // console.log('depth is ' + depth);
2347 // console.log('stack is ' + stack.length);
2348 stack.splice(0, depth);
2349 }
1936 2350
1937function isSetup() {2351 var result = {
1938 if (!hasJSON) return false; // needs JSON support2352 'name': ex.name,
1939 if (!globalServer) {2353 'message': ex.message,
1940 if (!ravenNotConfiguredError)2354 'url': getLocationHref(),
1941 logDebug('error', 'Error: Raven has not been configured.');2355 'stack': stack
1942 ravenNotConfiguredError = true;2356 };
1943 return false;2357 augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
2358 return result;
1944 }2359 }
1945 return true;
1946}
1947 2360
1948function joinRegExp(patterns) {2361 /**
1949 // Combine an array of regular expressions and strings into one large regexp2362 * Computes a stack trace for an exception.
1950 // Be mad.2363 * @param {Error} ex
1951 var sources = [],2364 * @param {(string|number)=} depth
1952 i = 0, len = patterns.length,2365 */
1953 pattern;2366 function computeStackTrace(ex, depth) {
2367 var stack = null;
2368 depth = (depth == null ? 0 : +depth);
1954 2369
1955 for (; i < len; i++) {2370 try {
1956 pattern = patterns[i];2371 // This must be tried first because Opera 10 *destroys*
1957 if (isString(pattern)) {2372 // its stacktrace property if you try to access the stack
1958 // If it's a string, we need to escape it2373 // property first!!
1959 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions2374 stack = computeStackTraceFromStacktraceProp(ex);
1960 sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"));2375 if (stack) {
1961 } else if (pattern && pattern.source) {2376 return stack;
1962 // If it's a regexp already, we want to extract the source2377 }
1963 sources.push(pattern.source);2378 } catch (e) {
2379 if (TraceKit.debug) {
2380 throw e;
2381 }
1964 }2382 }
1965 // Intentionally skip other cases
1966 }
1967 return new RegExp(sources.join('|'), 'i');
1968}
1969
1970function uuid4() {
1971 var crypto = window.crypto || window.msCrypto;
1972
1973 if (!isUndefined(crypto) && crypto.getRandomValues) {
1974 // Use window.crypto API if available
1975 var arr = new Uint16Array(8);
1976 crypto.getRandomValues(arr);
1977
1978 // set 4 in byte 7
1979 arr[3] = arr[3] & 0xFFF | 0x4000;
1980 // set 2 most significant bits of byte 9 to '10'
1981 arr[4] = arr[4] & 0x3FFF | 0x8000;
1982 2383
1983 var pad = function(num) {2384 try {
1984 var v = num.toString(16);2385 stack = computeStackTraceFromStackProp(ex);
1985 while (v.length < 4) {2386 if (stack) {
1986 v = '0' + v;2387 return stack;
1987 }2388 }
1988 return v;2389 } catch (e) {
1989 };2390 if (TraceKit.debug) {
1990 2391 throw e;
1991 return (pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) +2392 }
1992 pad(arr[5]) + pad(arr[6]) + pad(arr[7]));2393 }
1993 } else {
1994 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
1995 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1996 var r = Math.random()*16|0,
1997 v = c == 'x' ? r : (r&0x3|0x8);
1998 return v.toString(16);
1999 });
2000 }
2001}
2002
2003function logDebug(level) {
2004 if (originalConsoleMethods[level] && Raven.debug) {
2005 // _slice is coming from vendor/TraceKit/tracekit.js
2006 // so it's accessible globally
2007 originalConsoleMethods[level].apply(originalConsole, _slice.call(arguments, 1));
2008 }
2009}
2010 2394
2011function afterLoad() {2395 try {
2012 // Attempt to initialize Raven on load2396 stack = computeStackTraceFromOperaMultiLineMessage(ex);
2013 var RavenConfig = window.RavenConfig;2397 if (stack) {
2014 if (RavenConfig) {2398 return stack;
2015 Raven.config(RavenConfig.dsn, RavenConfig.config).install();2399 }
2016 }2400 } catch (e) {
2017}2401 if (TraceKit.debug) {
2402 throw e;
2403 }
2404 }
2018 2405
2019function urlencode(o) {2406 try {
2020 var pairs = [];2407 stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
2021 each(o, function(key, value) {2408 if (stack) {
2022 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));2409 return stack;
2023 });2410 }
2024 return pairs.join('&');2411 } catch (e) {
2025}2412 if (TraceKit.debug) {
2413 throw e;
2414 }
2415 }
2026 2416
2027function mergeContext(key, context) {2417 return {
2028 if (isUndefined(context)) {2418 'name': ex.name,
2029 delete globalContext[key];2419 'message': ex.message,
2030 } else {2420 'url': getLocationHref()
2031 globalContext[key] = objectMerge(globalContext[key] || {}, context);2421 };
2032 }2422 }
2033}
2034 2423
2035afterLoad();2424 computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
2425 computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp;
2426 computeStackTrace.guessFunctionName = guessFunctionName;
2427 computeStackTrace.gatherContext = gatherContext;
2036 2428
2037// This is being exposed no matter what because there are too many weird2429 return computeStackTrace;
2038// usecases for how people use Raven. If this is really a problem, I'm sorry.2430}());
2039window.Raven = Raven;
2040 2431
2041// Expose Raven to the world2432module.exports = TraceKit;
2042if (typeof define === 'function' && define.amd) {
2043 // AMD
2044 define('raven', [], function() {
2045 return Raven;
2046 });
2047} else if (typeof module === 'object') {
2048 // browserify
2049 module.exports = Raven;
2050} else if (typeof exports === 'object') {
2051 // CommonJS
2052 exports = Raven;
2053}
2054 2433
2055})(typeof window !== 'undefined' ? window : this);2434},{"4":4}]},{},[3])(3)
2435});