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,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);