raven.js 58 KB

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