Commit b29ee282a2043647dfbce69ceb3e48b6567aa4ac

Authored by Stephen Crosby
1 parent ea3a4590
Exists in master and in 1 other branch production

fixes #814 remove notifier.js

Showing 1 changed file with 0 additions and 1218 deletions   Show diff stats
public/javascripts/notifier.js
... ... @@ -1,1218 +0,0 @@
1   -// Airbrake JavaScript Notifier Bundle
2   -(function(window, document, undefined) {
3   -// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
4   -// Luke Smith http://lucassmith.name/ (2008)
5   -// Loic Dachary <loic@dachary.org> (2008)
6   -// Johan Euphrosine <proppy@aminche.com> (2008)
7   -// Øyvind Sean Kinsey http://kinsey.no/blog (2010)
8   -// Victor Homyakov (2010)
9   -//
10   -// Information and discussions
11   -// http://jspoker.pokersource.info/skin/test-printstacktrace.html
12   -// http://eriwen.com/javascript/js-stack-trace/
13   -// http://eriwen.com/javascript/stacktrace-update/
14   -// http://pastie.org/253058
15   -//
16   -// guessFunctionNameFromLines comes from firebug
17   -//
18   -// Software License Agreement (BSD License)
19   -//
20   -// Copyright (c) 2007, Parakey Inc.
21   -// All rights reserved.
22   -//
23   -// Redistribution and use of this software in source and binary forms, with or without modification,
24   -// are permitted provided that the following conditions are met:
25   -//
26   -// * Redistributions of source code must retain the above
27   -// copyright notice, this list of conditions and the
28   -// following disclaimer.
29   -//
30   -// * Redistributions in binary form must reproduce the above
31   -// copyright notice, this list of conditions and the
32   -// following disclaimer in the documentation and/or other
33   -// materials provided with the distribution.
34   -//
35   -// * Neither the name of Parakey Inc. nor the names of its
36   -// contributors may be used to endorse or promote products
37   -// derived from this software without specific prior
38   -// written permission of Parakey Inc.
39   -//
40   -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
41   -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
42   -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
43   -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44   -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45   -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
46   -// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   -// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48   -
49   -/**
50   - * Main function giving a function stack trace with a forced or passed in Error
51   - *
52   - * @cfg {Error} e The error to create a stacktrace from (optional)
53   - * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
54   - * @return {Array} of Strings with functions, lines, files, and arguments where possible
55   - */
56   -function printStackTrace(options) {
57   - options = options || {guess: true};
58   - var ex = options.e || null, guess = !!options.guess;
59   - var p = new printStackTrace.implementation(), result = p.run(ex);
60   - return (guess) ? p.guessAnonymousFunctions(result) : result;
61   -}
62   -
63   -if (typeof module !== "undefined" && module.exports) {
64   - module.exports = printStackTrace;
65   -}
66   -
67   -printStackTrace.implementation = function() {
68   -};
69   -
70   -printStackTrace.implementation.prototype = {
71   - /**
72   - * @param {Error} ex The error to create a stacktrace from (optional)
73   - * @param {String} mode Forced mode (optional, mostly for unit tests)
74   - */
75   - run: function(ex, mode) {
76   - ex = ex || this.createException();
77   - // examine exception properties w/o debugger
78   - //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
79   - mode = mode || this.mode(ex);
80   - if (mode === 'other') {
81   - return this.other(arguments.callee);
82   - } else {
83   - return this[mode](ex);
84   - }
85   - },
86   -
87   - createException: function() {
88   - try {
89   - this.undef();
90   - } catch (e) {
91   - return e;
92   - }
93   - },
94   -
95   - /**
96   - * Mode could differ for different exception, e.g.
97   - * exceptions in Chrome may or may not have arguments or stack.
98   - *
99   - * @return {String} mode of operation for the exception
100   - */
101   - mode: function(e) {
102   - if (e['arguments'] && e.stack) {
103   - return 'chrome';
104   - } else if (e.stack && e.sourceURL) {
105   - return 'safari';
106   - } else if (e.stack && e.number) {
107   - return 'ie';
108   - } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
109   - // e.message.indexOf("Backtrace:") > -1 -> opera
110   - // !e.stacktrace -> opera
111   - if (!e.stacktrace) {
112   - return 'opera9'; // use e.message
113   - }
114   - // 'opera#sourceloc' in e -> opera9, opera10a
115   - if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
116   - return 'opera9'; // use e.message
117   - }
118   - // e.stacktrace && !e.stack -> opera10a
119   - if (!e.stack) {
120   - return 'opera10a'; // use e.stacktrace
121   - }
122   - // e.stacktrace && e.stack -> opera10b
123   - if (e.stacktrace.indexOf("called from line") < 0) {
124   - return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
125   - }
126   - // e.stacktrace && e.stack -> opera11
127   - return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
128   - } else if (e.stack && !e.fileName) {
129   - // Chrome 27 does not have e.arguments as earlier versions,
130   - // but still does not have e.fileName as Firefox
131   - return 'chrome';
132   - } else if (e.stack) {
133   - return 'firefox';
134   - }
135   - return 'other';
136   - },
137   -
138   - /**
139   - * Given a context, function name, and callback function, overwrite it so that it calls
140   - * printStackTrace() first with a callback and then runs the rest of the body.
141   - *
142   - * @param {Object} context of execution (e.g. window)
143   - * @param {String} functionName to instrument
144   - * @param {Function} callback function to call with a stack trace on invocation
145   - */
146   - instrumentFunction: function(context, functionName, callback) {
147   - context = context || window;
148   - var original = context[functionName];
149   - context[functionName] = function instrumented() {
150   - callback.call(this, printStackTrace().slice(4));
151   - return context[functionName]._instrumented.apply(this, arguments);
152   - };
153   - context[functionName]._instrumented = original;
154   - },
155   -
156   - /**
157   - * Given a context and function name of a function that has been
158   - * instrumented, revert the function to it's original (non-instrumented)
159   - * state.
160   - *
161   - * @param {Object} context of execution (e.g. window)
162   - * @param {String} functionName to de-instrument
163   - */
164   - deinstrumentFunction: function(context, functionName) {
165   - if (context[functionName].constructor === Function &&
166   - context[functionName]._instrumented &&
167   - context[functionName]._instrumented.constructor === Function) {
168   - context[functionName] = context[functionName]._instrumented;
169   - }
170   - },
171   -
172   - /**
173   - * Given an Error object, return a formatted Array based on Chrome's stack string.
174   - *
175   - * @param e - Error object to inspect
176   - * @return Array<String> of function calls, files and line numbers
177   - */
178   - chrome: function(e) {
179   - var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
180   - replace(/^\s+(at eval )?at\s+/gm, '').
181   - replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
182   - replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
183   - stack.pop();
184   - return stack;
185   - },
186   -
187   - /**
188   - * Given an Error object, return a formatted Array based on Safari's stack string.
189   - *
190   - * @param e - Error object to inspect
191   - * @return Array<String> of function calls, files and line numbers
192   - */
193   - safari: function(e) {
194   - return e.stack.replace(/\[native code\]\n/m, '')
195   - .replace(/^(?=\w+Error\:).*$\n/m, '')
196   - .replace(/^@/gm, '{anonymous}()@')
197   - .split('\n');
198   - },
199   -
200   - /**
201   - * Given an Error object, return a formatted Array based on IE's stack string.
202   - *
203   - * @param e - Error object to inspect
204   - * @return Array<String> of function calls, files and line numbers
205   - */
206   - ie: function(e) {
207   - var lineRE = /^.*at (\w+) \(([^\)]+)\)$/gm;
208   - return e.stack.replace(/at Anonymous function /gm, '{anonymous}()@')
209   - .replace(/^(?=\w+Error\:).*$\n/m, '')
210   - .replace(lineRE, '$1@$2')
211   - .split('\n');
212   - },
213   -
214   - /**
215   - * Given an Error object, return a formatted Array based on Firefox's stack string.
216   - *
217   - * @param e - Error object to inspect
218   - * @return Array<String> of function calls, files and line numbers
219   - */
220   - firefox: function(e) {
221   - return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^[\(@]/gm, '{anonymous}()@').split('\n');
222   - },
223   -
224   - opera11: function(e) {
225   - var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
226   - var lines = e.stacktrace.split('\n'), result = [];
227   -
228   - for (var i = 0, len = lines.length; i < len; i += 2) {
229   - var match = lineRE.exec(lines[i]);
230   - if (match) {
231   - var location = match[4] + ':' + match[1] + ':' + match[2];
232   - var fnName = match[3] || "global code";
233   - fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
234   - result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
235   - }
236   - }
237   -
238   - return result;
239   - },
240   -
241   - opera10b: function(e) {
242   - // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
243   - // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
244   - // "@file://localhost/G:/js/test/functional/testcase1.html:15"
245   - var lineRE = /^(.*)@(.+):(\d+)$/;
246   - var lines = e.stacktrace.split('\n'), result = [];
247   -
248   - for (var i = 0, len = lines.length; i < len; i++) {
249   - var match = lineRE.exec(lines[i]);
250   - if (match) {
251   - var fnName = match[1]? (match[1] + '()') : "global code";
252   - result.push(fnName + '@' + match[2] + ':' + match[3]);
253   - }
254   - }
255   -
256   - return result;
257   - },
258   -
259   - /**
260   - * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
261   - *
262   - * @param e - Error object to inspect
263   - * @return Array<String> of function calls, files and line numbers
264   - */
265   - opera10a: function(e) {
266   - // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
267   - // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
268   - var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
269   - var lines = e.stacktrace.split('\n'), result = [];
270   -
271   - for (var i = 0, len = lines.length; i < len; i += 2) {
272   - var match = lineRE.exec(lines[i]);
273   - if (match) {
274   - var fnName = match[3] || ANON;
275   - result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
276   - }
277   - }
278   -
279   - return result;
280   - },
281   -
282   - // Opera 7.x-9.2x only!
283   - opera9: function(e) {
284   - // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
285   - // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
286   - var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
287   - var lines = e.message.split('\n'), result = [];
288   -
289   - for (var i = 2, len = lines.length; i < len; i += 2) {
290   - var match = lineRE.exec(lines[i]);
291   - if (match) {
292   - result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
293   - }
294   - }
295   -
296   - return result;
297   - },
298   -
299   - // Safari 5-, IE 9-, and others
300   - other: function(curr) {
301   - var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
302   - while (curr && curr['arguments'] && stack.length < maxStackSize) {
303   - fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
304   - args = Array.prototype.slice.call(curr['arguments'] || []);
305   - stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
306   - curr = curr.caller;
307   - }
308   - return stack;
309   - },
310   -
311   - /**
312   - * Given arguments array as a String, substituting type names for non-string types.
313   - *
314   - * @param {Arguments,Array} args
315   - * @return {String} stringified arguments
316   - */
317   - stringifyArguments: function(args) {
318   - var result = [];
319   - var slice = Array.prototype.slice;
320   - for (var i = 0; i < args.length; ++i) {
321   - var arg = args[i];
322   - if (arg === undefined) {
323   - result[i] = 'undefined';
324   - } else if (arg === null) {
325   - result[i] = 'null';
326   - } else if (arg.constructor) {
327   - if (arg.constructor === Array) {
328   - if (arg.length < 3) {
329   - result[i] = '[' + this.stringifyArguments(arg) + ']';
330   - } else {
331   - result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
332   - }
333   - } else if (arg.constructor === Object) {
334   - result[i] = '#object';
335   - } else if (arg.constructor === Function) {
336   - result[i] = '#function';
337   - } else if (arg.constructor === String) {
338   - result[i] = '"' + arg + '"';
339   - } else if (arg.constructor === Number) {
340   - result[i] = arg;
341   - }
342   - }
343   - }
344   - return result.join(',');
345   - },
346   -
347   - sourceCache: {},
348   -
349   - /**
350   - * @return the text from a given URL
351   - */
352   - ajax: function(url) {
353   - var req = this.createXMLHTTPObject();
354   - if (req) {
355   - try {
356   - req.open('GET', url, false);
357   - //req.overrideMimeType('text/plain');
358   - //req.overrideMimeType('text/javascript');
359   - req.send(null);
360   - //return req.status == 200 ? req.responseText : '';
361   - return req.responseText;
362   - } catch (e) {
363   - }
364   - }
365   - return '';
366   - },
367   -
368   - /**
369   - * Try XHR methods in order and store XHR factory.
370   - *
371   - * @return <Function> XHR function or equivalent
372   - */
373   - createXMLHTTPObject: function() {
374   - var xmlhttp, XMLHttpFactories = [
375   - function() {
376   - return new XMLHttpRequest();
377   - }, function() {
378   - return new ActiveXObject('Msxml2.XMLHTTP');
379   - }, function() {
380   - return new ActiveXObject('Msxml3.XMLHTTP');
381   - }, function() {
382   - return new ActiveXObject('Microsoft.XMLHTTP');
383   - }
384   - ];
385   - for (var i = 0; i < XMLHttpFactories.length; i++) {
386   - try {
387   - xmlhttp = XMLHttpFactories[i]();
388   - // Use memoization to cache the factory
389   - this.createXMLHTTPObject = XMLHttpFactories[i];
390   - return xmlhttp;
391   - } catch (e) {
392   - }
393   - }
394   - },
395   -
396   - /**
397   - * Given a URL, check if it is in the same domain (so we can get the source
398   - * via Ajax).
399   - *
400   - * @param url <String> source url
401   - * @return <Boolean> False if we need a cross-domain request
402   - */
403   - isSameDomain: function(url) {
404   - return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
405   - },
406   -
407   - /**
408   - * Get source code from given URL if in the same domain.
409   - *
410   - * @param url <String> JS source URL
411   - * @return <Array> Array of source code lines
412   - */
413   - getSource: function(url) {
414   - // TODO reuse source from script tags?
415   - if (!(url in this.sourceCache)) {
416   - this.sourceCache[url] = this.ajax(url).split('\n');
417   - }
418   - return this.sourceCache[url];
419   - },
420   -
421   - guessAnonymousFunctions: function(stack) {
422   - for (var i = 0; i < stack.length; ++i) {
423   - var reStack = /\{anonymous\}\(.*\)@(.*)/,
424   - reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
425   - frame = stack[i], ref = reStack.exec(frame);
426   -
427   - if (ref) {
428   - var m = reRef.exec(ref[1]);
429   - if (m) { // If falsey, we did not get any file/line information
430   - var file = m[1], lineno = m[2], charno = m[3] || 0;
431   - if (file && this.isSameDomain(file) && lineno) {
432   - var functionName = this.guessAnonymousFunction(file, lineno, charno);
433   - stack[i] = frame.replace('{anonymous}', functionName);
434   - }
435   - }
436   - }
437   - }
438   - return stack;
439   - },
440   -
441   - guessAnonymousFunction: function(url, lineNo, charNo) {
442   - var ret;
443   - try {
444   - ret = this.findFunctionName(this.getSource(url), lineNo);
445   - } catch (e) {
446   - ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
447   - }
448   - return ret;
449   - },
450   -
451   - findFunctionName: function(source, lineNo) {
452   - // FIXME findFunctionName fails for compressed source
453   - // (more than one function on the same line)
454   - // function {name}({args}) m[1]=name m[2]=args
455   - var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
456   - // {name} = function ({args}) TODO args capture
457   - // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
458   - var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
459   - // {name} = eval()
460   - var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
461   - // Walk backwards in the source lines until we find
462   - // the line which matches one of the patterns above
463   - var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
464   - for (var i = 0; i < maxLines; ++i) {
465   - // lineNo is 1-based, source[] is 0-based
466   - line = source[lineNo - i - 1];
467   - commentPos = line.indexOf('//');
468   - if (commentPos >= 0) {
469   - line = line.substr(0, commentPos);
470   - }
471   - // TODO check other types of comments? Commented code may lead to false positive
472   - if (line) {
473   - code = line + code;
474   - m = reFunctionExpression.exec(code);
475   - if (m && m[1]) {
476   - return m[1];
477   - }
478   - m = reFunctionDeclaration.exec(code);
479   - if (m && m[1]) {
480   - //return m[1] + "(" + (m[2] || "") + ")";
481   - return m[1];
482   - }
483   - m = reFunctionEvaluation.exec(code);
484   - if (m && m[1]) {
485   - return m[1];
486   - }
487   - }
488   - }
489   - return '(?)';
490   - }
491   -};// Airbrake JavaScript Notifier
492   -(function() {
493   - "use strict";
494   -
495   - var NOTICE_XML = '<?xml version="1.0" encoding="UTF-8"?>' +
496   - '<notice version="2.0">' +
497   - '<api-key>{key}</api-key>' +
498   - '<notifier>' +
499   - '<name>airbrake_js</name>' +
500   - '<version>0.2.0</version>' +
501   - '<url>http://airbrake.io</url>' +
502   - '</notifier>' +
503   - '<error>' +
504   - '<class>{exception_class}</class>' +
505   - '<message><![CDATA[{exception_message}]]></message>' +
506   - '<backtrace>{backtrace_lines}</backtrace>' +
507   - '</error>' +
508   - '<request>' +
509   - '<url><![CDATA[{request_url}]]></url>' +
510   - '<component>{request_component}</component>' +
511   - '<action>{request_action}</action>' +
512   - '{request}' +
513   - '</request>' +
514   - '<server-environment>' +
515   - '<project-root>{project_root}</project-root>' +
516   - '<environment-name>{environment}</environment-name>' +
517   - '<app-version>{appVersion}</app-version>' +
518   - '</server-environment>' +
519   - '<current-user>' +
520   - '<id>{user_id}</id>' +
521   - '<name>{user_name}</name>' +
522   - '<email>{user_email}</email>' +
523   - '</current-user>' +
524   - '</notice>',
525   - REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>',
526   - REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>',
527   - BACKTRACE_LINE_XML = '<line method="{function}" file="{file}" number="{line}" />',
528   - Config,
529   - Global,
530   - Util,
531   - _publicAPI,
532   -
533   - NOTICE_JSON = {
534   - "notifier": {
535   - "name": "airbrake_js",
536   - "version": "0.2.0",
537   - "url": "http://airbrake.io"
538   - },
539   - "error": [
540   - {
541   - "type": "{exception_class}",
542   - "message": "{exception_message}",
543   - "backtrace": []
544   -
545   - }
546   - ],
547   - "context": {
548   - "language": "JavaScript",
549   - "environment": "{environment}",
550   -
551   - "version": "1.1.1",
552   - "url": "{request_url}",
553   - "rootDirectory": "{project_root}",
554   - "action": "{request_action}",
555   - "app-version": "{appVersion}",
556   -
557   - "userId": "{user_id}",
558   - "userName": "{user_name}",
559   - "userEmail": "{user_email}"
560   - },
561   - "environment": {},
562   - //"session": "",
563   - "params": {}
564   - };
565   -
566   - Util = {
567   - /*
568   - * Merge a number of objects into one.
569   - *
570   - * Usage example:
571   - * var obj1 = {
572   - * a: 'a'
573   - * },
574   - * obj2 = {
575   - * b: 'b'
576   - * },
577   - * obj3 = {
578   - * c: 'c'
579   - * },
580   - * mergedObj = Util.merge(obj1, obj2, obj3);
581   - *
582   - * mergedObj is: {
583   - * a: 'a',
584   - * b: 'b',
585   - * c: 'c'
586   - * }
587   - *
588   - */
589   - merge: (function() {
590   - function processProperty (key, dest, src) {
591   - if (src.hasOwnProperty(key)) {
592   - dest[key] = src[key];
593   - }
594   - }
595   -
596   - return function() {
597   - var objects = Array.prototype.slice.call(arguments),
598   - obj,
599   - key,
600   - result = {};
601   -
602   - while (obj = objects.shift()) {
603   - for (key in obj) {
604   - processProperty(key, result, obj);
605   - }
606   - }
607   -
608   - return result;
609   - };
610   - })(),
611   -
612   - /*
613   - * Replace &, <, >, ', " characters with correspondent HTML entities.
614   - */
615   - escape: function (text) {
616   - return text.replace(/&/g, '&#38;').replace(/</g, '&#60;').replace(/>/g, '&#62;')
617   - .replace(/'/g, '&#39;').replace(/"/g, '&#34;');
618   - },
619   -
620   - /*
621   - * Remove leading and trailing space characters.
622   - */
623   - trim: function (text) {
624   - return text.toString().replace(/^\s+/, '').replace(/\s+$/, '');
625   - },
626   -
627   - /*
628   - * Fill 'text' pattern with 'data' values.
629   - *
630   - * e.g. Utils.substitute('<{tag}></{tag}>', {tag: 'div'}, true) will return '<div></div>'
631   - *
632   - * emptyForUndefinedData - a flag, if true, all matched {<name>} without data.<name> value specified will be
633   - * replaced with empty string.
634   - */
635   - substitute: function (text, data, emptyForUndefinedData) {
636   - return text.replace(/{([\w_.-]+)}/g, function(match, key) {
637   - return (key in data) ? data[key] : (emptyForUndefinedData ? '' : match);
638   - });
639   - },
640   -
641   - /*
642   - * Perform pattern rendering for an array of data objects.
643   - * Returns a concatenation of rendered strings of all objects in array.
644   - */
645   - substituteArr: function (text, dataArr, emptyForUndefinedData) {
646   - var _i = 0, _l = 0,
647   - returnStr = '';
648   -
649   - for (_i = 0, _l = dataArr.length; _i < _l; _i += 1) {
650   - returnStr += this.substitute(text, dataArr[_i], emptyForUndefinedData);
651   - }
652   -
653   - return returnStr;
654   - },
655   -
656   - /*
657   - * Add hook for jQuery.fn.on function, to manualy call window.Airbrake.captureException() method
658   - * for every exception occurred.
659   - *
660   - * Let function 'f' be binded as an event handler:
661   - *
662   - * $(window).on 'click', f
663   - *
664   - * If an exception is occurred inside f's body, it will be catched here
665   - * and forwarded to captureException method.
666   - *
667   - * processjQueryEventHandlerWrapping is called every time window.Airbrake.setTrackJQ method is used,
668   - * if it switches previously setted value.
669   - */
670   - processjQueryEventHandlerWrapping: function () {
671   - if (Config.options.trackJQ === true) {
672   - Config.jQuery_fn_on_original = Config.jQuery_fn_on_original || jQuery.fn.on;
673   -
674   - jQuery.fn.on = function () {
675   - var args = Array.prototype.slice.call(arguments),
676   - fnArgIdx = 4;
677   -
678   - // Search index of function argument
679   - while((--fnArgIdx > -1) && (typeof args[fnArgIdx] !== 'function'));
680   -
681   - // If the function is not found, then subscribe original event handler function
682   - if (fnArgIdx === -1) {
683   - return Config.jQuery_fn_on_original.apply(this, arguments);
684   - }
685   -
686   - // If the function is found, then subscribe wrapped event handler function
687   - args[fnArgIdx] = (function (fnOriginHandler) {
688   - return function() {
689   - try {
690   - fnOriginHandler.apply(this, arguments);
691   - } catch (e) {
692   - Global.captureException(e);
693   - }
694   - };
695   - })(args[fnArgIdx]);
696   -
697   - // Call original jQuery.fn.on, with the same list of arguments, but
698   - // a function replaced with a proxy.
699   - return Config.jQuery_fn_on_original.apply(this, args);
700   - };
701   - } else {
702   - // Recover original jQuery.fn.on if Config.options.trackJQ is set to false
703   - (typeof Config.jQuery_fn_on_original === 'function') && (jQuery.fn.on = Config.jQuery_fn_on_original);
704   - }
705   - },
706   -
707   - isjQueryPresent: function () {
708   - // Currently only 1.7.x version supported
709   - return (typeof jQuery === 'function') && ('fn' in jQuery) && ('jquery' in jQuery.fn)
710   - && (jQuery.fn.jquery.indexOf('1.7') === 0)
711   - },
712   -
713   - /*
714   - * Make first letter in a string capital. e.g. 'guessFunctionName' -> 'GuessFunctionName'
715   - * Is used to generate getter and setter method names.
716   - */
717   - capitalizeFirstLetter: function (str) {
718   - return str.charAt(0).toUpperCase() + str.slice(1);
719   - },
720   -
721   - /*
722   - * Generate public API from an array of specifically formated objects, e.g.
723   - *
724   - * - this will generate 'setEnvironment' and 'getEnvironment' API methods for configObj.xmlData.environment variable:
725   - * {
726   - * variable: 'environment',
727   - * namespace: 'xmlData'
728   - * }
729   - *
730   - * - this will define 'method' function as 'captureException' API method
731   - * {
732   - * methodName: 'captureException',
733   - * method: (function (...) {...});
734   - * }
735   - *
736   - */
737   - generatePublicAPI: (function () {
738   - function _generateSetter (variable, namespace, configObj) {
739   - return function (value) {
740   - configObj[namespace][variable] = value;
741   - };
742   - }
743   -
744   - function _generateGetter (variable, namespace, configObj) {
745   - return function (value) {
746   - return configObj[namespace][variable];
747   - };
748   - }
749   -
750   - /*
751   - * publicAPI: array of specifically formated objects
752   - * configObj: inner configuration object
753   - */
754   - return function (publicAPI, configObj) {
755   - var _i = 0, _m = null, _capitalized = '',
756   - returnObj = {};
757   -
758   - for (_i = 0; _i < publicAPI.length; _i += 1) {
759   - _m = publicAPI[_i];
760   -
761   - switch (true) {
762   - case (typeof _m.variable !== 'undefined') && (typeof _m.methodName === 'undefined'):
763   - _capitalized = Util.capitalizeFirstLetter(_m.variable)
764   - returnObj['set' + _capitalized] = _generateSetter(_m.variable, _m.namespace, configObj);
765   - returnObj['get' + _capitalized] = _generateGetter(_m.variable, _m.namespace, configObj);
766   -
767   - break;
768   - case (typeof _m.methodName !== 'undefined') && (typeof _m.method !== 'undefined'):
769   - returnObj[_m.methodName] = _m.method
770   -
771   - break;
772   -
773   - default:
774   - }
775   - }
776   -
777   - return returnObj;
778   - };
779   - } ())
780   - };
781   -
782   - /*
783   - * The object to store settings. Allocated from the Global (windows scope) so that users can change settings
784   - * only through the methods, rather than through a direct change of the object fileds. So that we can to handle
785   - * change settings event (in setter method).
786   - */
787   - Config = {
788   - xmlData: {
789   - environment: 'environment'
790   - },
791   -
792   - options: {
793   - trackJQ: false, // jQuery.fn.jquery
794   - host: 'api.airbrake.io',
795   - errorDefaults: {},
796   - guessFunctionName: false,
797   - requestType: 'GET', // Can be 'POST' or 'GET'
798   - outputFormat: 'XML' // Can be 'XML' or 'JSON'
799   - }
800   - };
801   -
802   - /*
803   - * The public API definition object. If no 'methodName' and 'method' values specified,
804   - * getter and setter for 'variable' will be defined.
805   - */
806   - _publicAPI = [
807   - {
808   - variable: 'environment',
809   - namespace: 'xmlData'
810   - }, {
811   - variable: 'key',
812   - namespace: 'xmlData'
813   - }, {
814   - variable: 'host',
815   - namespace: 'options'
816   - },{
817   - variable: 'projectId',
818   - namespace: 'options'
819   - },{
820   - variable: 'errorDefaults',
821   - namespace: 'options'
822   - }, {
823   - variable: 'guessFunctionName',
824   - namespace: 'options'
825   - }, {
826   - variable: 'outputFormat',
827   - namespace: 'options'
828   - }, {
829   - methodName: 'setCurrentUser',
830   - method: (function (value) {
831   - for (var key in value) {
832   - if (value.hasOwnProperty(key)) {
833   - Config.xmlData['user_' + key] = value[key];
834   - }
835   - }
836   - })
837   - }, {
838   - methodName: 'setTrackJQ',
839   - variable: 'trackJQ',
840   - namespace: 'options',
841   - method: (function (value) {
842   - if (!Util.isjQueryPresent()) {
843   - throw Error('Please do not call \'Airbrake.setTrackJQ\' if jQuery does\'t present');
844   - }
845   -
846   - value = !!value;
847   -
848   - if (Config.options.trackJQ === value) {
849   - return;
850   - }
851   -
852   - Config.options.trackJQ = value;
853   -
854   - Util.processjQueryEventHandlerWrapping();
855   - })
856   - }, {
857   - methodName: 'captureException',
858   - method: (function (e) {
859   - new Notifier().notify({
860   - message: e.message,
861   - stack: e.stack
862   - });
863   - })
864   - }, {
865   - variable: 'appVersion',
866   - namespace: 'xmlData'
867   - }
868   - ];
869   -
870   - // Share to global scope as Airbrake ("window.Hoptoad" for backward compatibility)
871   - Global = window.Airbrake = window.Hoptoad = Util.generatePublicAPI(_publicAPI, Config);
872   -
873   - Global._filters = [];
874   -
875   - Global.addFilter = function (cb) {
876   - Global._filters.push(cb);
877   - };
878   -
879   - function Notifier() {
880   - this.options = Util.merge({}, Config.options);
881   - this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData);
882   - }
883   -
884   - Notifier.prototype = {
885   - constructor: Notifier,
886   - VERSION: '0.2.0',
887   - ROOT: window.location.protocol + '//' + window.location.host,
888   - BACKTRACE_MATCHER: /^(.*)\@(.*)\:(\d+)$/,
889   - backtrace_filters: [/notifier\.js/],
890   - DEF_XML_DATA: {
891   - request: {}
892   - },
893   -
894   - notify: (function () {
895   - /*
896   - * Emit GET request via <iframe> element.
897   - * Data is transmited as a part of query string.
898   - */
899   - function _sendGETRequest (url, data) {
900   - var request = document.createElement('iframe');
901   -
902   - request.style.display = 'none';
903   - request.src = url + '?data=' + data;
904   -
905   - // When request has been sent, delete iframe
906   - request.onload = function () {
907   - // To avoid infinite progress indicator
908   - setTimeout(function() {
909   - document.body.removeChild(request);
910   - }, 0);
911   - };
912   -
913   - document.body.appendChild(request);
914   - }
915   -
916   - /*
917   - * Cross-domain AJAX POST request.
918   - *
919   - * It requires a server setup as described in Cross-Origin Resource Sharing spec:
920   - * http://www.w3.org/TR/cors/
921   - */
922   - function _sendPOSTRequest (url, data) {
923   - var request = new XMLHttpRequest();
924   - request.open('POST', url, true);
925   - request.setRequestHeader('Content-Type', 'application/json');
926   - request.send(data);
927   - }
928   -
929   - return function (error) {
930   - var outputData = '', jsonData,
931   - url = '';
932   - //
933   -
934   - /*
935   - * Should be changed to url = '//' + ...
936   - * to use the protocol of current page (http or https). Only sends 'secure' if page is secure.
937   - * XML uses V2 API. http://collect.airbrake.io/notifier_api/v2/notices
938   - */
939   -
940   -
941   - switch (this.options['outputFormat']) {
942   - case 'XML':
943   - jsonData = this.generateDataJSON(error);
944   - if (this.shouldSendData(jsonData)){
945   - outputData = encodeURIComponent(this.generateXML(jsonData));
946   - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices';
947   - _sendGETRequest(url, outputData);
948   - }
949   - break;
950   -
951   - case 'JSON':
952   - /*
953   - * JSON uses API V3. Needs project in URL.
954   - * http://collect.airbrake.io/api/v3/projects/[PROJECT_ID]/notices?key=[API_KEY]
955   - * url = window.location.protocol + '://' + this.options.host + '/api/v3/projects' + this.options.projectId + '/notices?key=' + this.options.key;
956   - */
957   - jsonData = this.generateDataJSON(error);
958   - if (this.shouldSendData(jsonData)){
959   - outputData = JSON.stringify(this.generateJSON(jsonData));
960   - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key;
961   - _sendPOSTRequest(url, outputData);
962   - }
963   - break;
964   -
965   - default:
966   - }
967   -
968   - };
969   - } ()),
970   -
971   - /*
972   - * Generate inner JSON representation of exception data that can be rendered as XML or JSON.
973   - */
974   - generateDataJSON: (function () {
975   - /*
976   - * Generate variables array for inputObj object.
977   - *
978   - * e.g.
979   - *
980   - * _generateVariables({a: 'a'}) -> [{key: 'a', value: 'a'}]
981   - *
982   - */
983   - function _generateVariables (inputObj) {
984   - var key = '', returnArr = [];
985   -
986   - for (key in inputObj) {
987   - if (inputObj.hasOwnProperty(key)) {
988   - returnArr.push({
989   - key: key,
990   - value: inputObj[key]
991   - });
992   - }
993   - }
994   -
995   - return returnArr;
996   - }
997   -
998   - /*
999   - * Generate Request part of notification.
1000   - */
1001   - function _composeRequestObj (methods, errorObj) {
1002   - var _i = 0,
1003   - returnObj = {},
1004   - type = '';
1005   -
1006   - for (_i = 0; _i < methods.length; _i += 1) {
1007   - type = methods[_i];
1008   - if (typeof errorObj[type] !== 'undefined') {
1009   - returnObj[type] = _generateVariables(errorObj[type]);
1010   - }
1011   - }
1012   -
1013   - return returnObj;
1014   - }
1015   -
1016   - return function (errorWithoutDefaults) {
1017   - /*
1018   - * A constructor line:
1019   - *
1020   - * this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData);
1021   - */
1022   - var outputData = this.xmlData,
1023   - error = Util.merge(this.options.errorDefaults, errorWithoutDefaults),
1024   -
1025   - component = error.component || '',
1026   - request_url = (error.url || '' + location.href),
1027   -
1028   - methods = ['cgi-data', 'params', 'session'],
1029   - _outputData = null;
1030   -
1031   - _outputData = {
1032   - request_url: request_url,
1033   - request_action: (error.action || ''),
1034   - request_component: component,
1035   - request: (function () {
1036   - if (request_url || component) {
1037   - error['cgi-data'] = error['cgi-data'] || {};
1038   - error['cgi-data'].HTTP_USER_AGENT = navigator.userAgent;
1039   - return Util.merge(outputData.request, _composeRequestObj(methods, error));
1040   - } else {
1041   - return {}
1042   - }
1043   - } ()),
1044   -
1045   - project_root: this.ROOT,
1046   - exception_class: (error.type || errorWithoutDefaults.type ||
1047   - (errorWithoutDefaults.constructor.name != "Object" ? errorWithoutDefaults.constructor.name : 'Error')),
1048   - exception_message: (error.message || errorWithoutDefaults.message || 'Unknown error.'),
1049   - backtrace_lines: this.generateBacktrace(errorWithoutDefaults)
1050   - }
1051   -
1052   - outputData = Util.merge(outputData, _outputData);
1053   -
1054   - return outputData;
1055   - };
1056   - } ()),
1057   -
1058   - /*
1059   - * Generate XML notification from inner JSON representation.
1060   - * NOTICE_XML is used as pattern.
1061   - */
1062   - generateXML: (function () {
1063   - function _generateRequestVariableGroups (requestObj) {
1064   - var _group = '',
1065   - returnStr = '';
1066   -
1067   - for (_group in requestObj) {
1068   - if (requestObj.hasOwnProperty(_group)) {
1069   - returnStr += Util.substitute(REQUEST_VARIABLE_GROUP_XML, {
1070   - group_name: _group,
1071   - inner_content: Util.substituteArr(REQUEST_VARIABLE_XML, requestObj[_group], true)
1072   - }, true);
1073   - }
1074   - }
1075   -
1076   - return returnStr;
1077   - }
1078   -
1079   - return function (JSONdataObj) {
1080   - JSONdataObj.request = _generateRequestVariableGroups(JSONdataObj.request);
1081   - JSONdataObj.backtrace_lines = Util.substituteArr(BACKTRACE_LINE_XML, JSONdataObj.backtrace_lines, true);
1082   -
1083   - return Util.substitute(NOTICE_XML, JSONdataObj, true);
1084   - };
1085   - } ()),
1086   -
1087   - /*
1088   - * Generate JSON notification from inner JSON representation.
1089   - * NOTICE_JSON is used as pattern.
1090   - */
1091   - generateJSON: function (JSONdataObj) {
1092   - // Pattern string is JSON.stringify(NOTICE_JSON)
1093   - // The rendered string is parsed back as JSON.
1094   - var outputJSON = JSON.parse(Util.substitute(JSON.stringify(NOTICE_JSON), JSONdataObj, true));
1095   -
1096   - // REMOVED - Request from JSON.
1097   - outputJSON.request = Util.merge(outputJSON.request, JSONdataObj.request);
1098   - outputJSON.error.backtrace = JSONdataObj.backtrace_lines;
1099   -
1100   - return outputJSON;
1101   - },
1102   -
1103   - generateBacktrace: function (error) {
1104   - var backtrace = [],
1105   - file,
1106   - i,
1107   - matches,
1108   - stacktrace;
1109   -
1110   - error = error || {};
1111   -
1112   - if (typeof error.stack !== 'string') {
1113   - try {
1114   - (0)();
1115   - } catch (e) {
1116   - error.stack = e.stack;
1117   - }
1118   - }
1119   -
1120   - stacktrace = this.getStackTrace(error);
1121   -
1122   - for (i = 0; i < stacktrace.length; i++) {
1123   - matches = stacktrace[i].match(this.BACKTRACE_MATCHER);
1124   -
1125   - if (matches && this.validBacktraceLine(stacktrace[i])) {
1126   - file = matches[2].replace(this.ROOT, '[PROJECT_ROOT]');
1127   -
1128   - if (i === 0 && matches[2].match(document.location.href)) {
1129   - // backtrace.push('<line method="" file="internal: " number=""/>');
1130   -
1131   - backtrace.push({
1132   - // Updated to fit in with V3 new terms for Backtrace data.
1133   - 'function': '',
1134   - file: 'internal: ',
1135   - line: ''
1136   - });
1137   - }
1138   -
1139   - // backtrace.push('<line method="' + Util.escape(matches[1]) + '" file="' + Util.escape(file) +
1140   - // '" number="' + matches[3] + '" />');
1141   -
1142   - backtrace.push({
1143   - 'function': Util.escape(matches[1]),
1144   - file: Util.escape(file),
1145   - line: matches[3]
1146   - });
1147   - }
1148   - }
1149   -
1150   - return backtrace;
1151   - },
1152   -
1153   - getStackTrace: function (error) {
1154   - var i,
1155   - stacktrace = printStackTrace({
1156   - e: error,
1157   - guess: this.options.guessFunctionName
1158   - });
1159   -
1160   - for (i = 0; i < stacktrace.length; i++) {
1161   - if (stacktrace[i].match(/\:\d+$/)) {
1162   - continue;
1163   - }
1164   -
1165   - // Special case for sprocket coffee stacktrace:
1166   - // "Function.foo (http://host/file.js?body=1:666:42)" becomes "Function.foo @http://host/file.js?body=1:666"
1167   - if (stacktrace[i].match(/\([^\s]+:(\d+):(\d+)\)$/)) {
1168   - stacktrace[i] = stacktrace[i].replace(/\((.+):(\d+):(\d+)\)$/, '@$1:$2')
1169   - continue;
1170   - }
1171   -
1172   - if (stacktrace[i].indexOf('@') === -1) {
1173   - stacktrace[i] += '@unsupported.js';
1174   - }
1175   -
1176   - stacktrace[i] += ':0';
1177   - }
1178   -
1179   - return stacktrace;
1180   - },
1181   -
1182   - validBacktraceLine: function (line) {
1183   - for (var i = 0; i < this.backtrace_filters.length; i++) {
1184   - if (line.match(this.backtrace_filters[i])) {
1185   - return false;
1186   - }
1187   - }
1188   -
1189   - return true;
1190   - },
1191   -
1192   - shouldSendData: function (jsonData) {
1193   - var shouldSend = true, i;
1194   -
1195   - for ( i = 0; i < Global._filters.length; i++ ) {
1196   - if ( ! Global._filters[i](jsonData) ){
1197   - shouldSend = false;
1198   - }
1199   - }
1200   -
1201   - return shouldSend;
1202   - }
1203   - };
1204   -
1205   - var oldOnerror = window.onerror;
1206   - window.onerror = function (message, file, line, code, error) {
1207   - setTimeout(function () {
1208   - var e = error || {stack: '()@' + file + ':' + line}
1209   - e.message = message
1210   - new Notifier().notify(e);
1211   - }, 0);
1212   - if (oldOnerror) {
1213   - return oldOnerror(message, file, line, code, error);
1214   - }
1215   - return true;
1216   - };
1217   -})();
1218   -})(window, document);