raven.js 58 KB

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