Commit 9889b40deafa166ea6b483fef4a54799c3f7bc89
1 parent
3f0b112f
Exists in
master
and in
1 other branch
Update notifier.js from https://raw.github.com/airbrake/airbrake-js/master/dist/notifier.js
Showing
1 changed file
with
1001 additions
and
227 deletions
Show diff stats
public/javascripts/notifier.js
| 1 | -var Hoptoad = { | |
| 2 | - VERSION : '2.0', | |
| 3 | - NOTICE_XML : '<?xml version="1.0" encoding="UTF-8"?>\ | |
| 4 | - <notice version="2.0">\ | |
| 5 | - <api-key></api-key>\ | |
| 6 | - <notifier>\ | |
| 7 | - <name>errbit_notifier_js</name>\ | |
| 8 | - <version>2.0</version>\ | |
| 9 | - <url>https://github.com/errbit/errbit</url>\ | |
| 10 | - </notifier>\ | |
| 11 | - <error>\ | |
| 12 | - <class>EXCEPTION_CLASS</class>\ | |
| 13 | - <message>EXCEPTION_MESSAGE</message>\ | |
| 14 | - <backtrace>BACKTRACE_LINES</backtrace>\ | |
| 15 | - </error>\ | |
| 16 | - <request>\ | |
| 17 | - <url>REQUEST_URL</url>\ | |
| 18 | - <component>REQUEST_COMPONENT</component>\ | |
| 19 | - <action>REQUEST_ACTION</action>\ | |
| 20 | - </request>\ | |
| 21 | - <server-environment>\ | |
| 22 | - <project-root>PROJECT_ROOT</project-root>\ | |
| 23 | - <environment-name>production</environment-name>\ | |
| 24 | - </server-environment>\ | |
| 25 | - </notice>', | |
| 26 | - ROOT : window.location.protocol + '//' + window.location.host, | |
| 27 | - BACKTRACE_MATCHER : /^(.*)\@(.*)\:(\d+)$/, | |
| 28 | - backtrace_filters : [/notifier\.js/], | |
| 29 | - | |
| 30 | - notify: function(error) { | |
| 31 | - var xml = escape(Hoptoad.generateXML(error)); | |
| 32 | - var host = Hoptoad.host; | |
| 33 | - var url = '//' + host + '/notifier_api/v2/notices.xml?data=' + xml; | |
| 34 | - var request = document.createElement('iframe'); | |
| 35 | - | |
| 36 | - request.style.width = '1px'; | |
| 37 | - request.style.height = '1px'; | |
| 38 | - request.style.display = 'none'; | |
| 39 | - request.src = url; | |
| 40 | - | |
| 41 | - document.getElementsByTagName('head')[0].appendChild(request); | |
| 42 | - }, | |
| 43 | - | |
| 44 | - setEnvironment: function(value) { | |
| 45 | - var matcher = /<environment-name>.*<\/environment-name>/; | |
| 46 | - | |
| 47 | - Hoptoad.NOTICE_XML = Hoptoad.NOTICE_XML.replace(matcher, | |
| 48 | - '<environment-name>' + | |
| 49 | - value + | |
| 50 | - '</environment-name>') | |
| 51 | - }, | |
| 52 | - | |
| 53 | - setHost: function(value) { | |
| 54 | - Hoptoad.host = value; | |
| 55 | - }, | |
| 56 | - | |
| 57 | - setKey: function(value) { | |
| 58 | - var matcher = /<api-key>.*<\/api-key>/; | |
| 59 | - | |
| 60 | - Hoptoad.NOTICE_XML = Hoptoad.NOTICE_XML.replace(matcher, | |
| 61 | - '<api-key>' + | |
| 62 | - value + | |
| 63 | - '</api-key>'); | |
| 64 | - }, | |
| 65 | - | |
| 66 | - setErrorDefaults: function(value) { | |
| 67 | - Hoptoad.errorDefaults = value; | |
| 68 | - }, | |
| 69 | - | |
| 70 | - generateXML: function(errorWithoutDefaults) { | |
| 71 | - var error = Hoptoad.mergeDefault(Hoptoad.errorDefaults, errorWithoutDefaults); | |
| 72 | - | |
| 73 | - var xml = Hoptoad.NOTICE_XML; | |
| 74 | - var url = Hoptoad.escapeText(error.url || ''); | |
| 75 | - var component = Hoptoad.escapeText(error.component || ''); | |
| 76 | - var action = Hoptoad.escapeText(error.action || ''); | |
| 77 | - var type = Hoptoad.escapeText(error.type || 'Error'); | |
| 78 | - var message = Hoptoad.escapeText(error.message || 'Unknown error.'); | |
| 79 | - var backtrace = Hoptoad.generateBacktrace(error); | |
| 80 | - | |
| 81 | - | |
| 82 | - if (Hoptoad.trim(url) == '' && Hoptoad.trim(component) == '') { | |
| 83 | - xml = xml.replace(/<request>.*<\/request>/, ''); | |
| 84 | - } else { | |
| 85 | - var data = ''; | |
| 86 | - | |
| 87 | - var cgi_data = error['cgi-data'] || {}; | |
| 88 | - cgi_data["HTTP_USER_AGENT"] = navigator.userAgent; | |
| 89 | - data += '<cgi-data>'; | |
| 90 | - data += Hoptoad.generateVariables(cgi_data); | |
| 91 | - data += '</cgi-data>'; | |
| 92 | - | |
| 93 | - var methods = ['params', 'session']; | |
| 94 | - | |
| 95 | - for (var i = 0; i < methods.length; i++) { | |
| 96 | - var method = methods[i]; | |
| 97 | - | |
| 98 | - if (error[method]) { | |
| 99 | - data += '<' + method + '>'; | |
| 100 | - data += Hoptoad.generateVariables(error[method]); | |
| 101 | - data += '</' + method + '>'; | |
| 102 | - } | |
| 103 | - } | |
| 104 | - | |
| 105 | - xml = xml.replace('</request>', data + '</request>') | |
| 106 | - .replace('REQUEST_URL', url) | |
| 107 | - .replace('REQUEST_ACTION', action) | |
| 108 | - .replace('REQUEST_COMPONENT', component); | |
| 109 | - } | |
| 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. | |
| 110 | 48 | |
| 111 | - return xml.replace('PROJECT_ROOT', Hoptoad.ROOT) | |
| 112 | - .replace('EXCEPTION_CLASS', type) | |
| 113 | - .replace('EXCEPTION_MESSAGE', message) | |
| 114 | - .replace('BACKTRACE_LINES', backtrace.join('')); | |
| 115 | - }, | |
| 116 | - | |
| 117 | - generateBacktrace: function(error) { | |
| 118 | - error = error || {}; | |
| 119 | - | |
| 120 | - if (typeof error.stack != 'string') { | |
| 121 | - try { | |
| 122 | - (0)(); | |
| 123 | - } catch(e) { | |
| 124 | - error.stack = e.stack; | |
| 125 | - } | |
| 126 | - } | |
| 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 | + var ex = (options && options.e) ? options.e : null; | |
| 58 | + var guess = options ? !!options.guess : true; | |
| 59 | + | |
| 60 | + var p = new printStackTrace.implementation(); | |
| 61 | + var result = p.run(ex); | |
| 62 | + return (guess) ? p.guessFunctions(result) : result; | |
| 63 | +} | |
| 127 | 64 | |
| 128 | - var backtrace = []; | |
| 129 | - var stacktrace = Hoptoad.getStackTrace(error); | |
| 65 | +printStackTrace.implementation = function() {}; | |
| 130 | 66 | |
| 131 | - for (var i = 0, l = stacktrace.length; i < l; i++) { | |
| 132 | - var line = stacktrace[i]; | |
| 133 | - var matches = line.match(Hoptoad.BACKTRACE_MATCHER); | |
| 67 | +printStackTrace.implementation.prototype = { | |
| 68 | + run: function(ex) { | |
| 69 | + ex = ex || | |
| 70 | + (function() { | |
| 71 | + try { | |
| 72 | + var _err = __undef__ << 1; | |
| 73 | + } catch (e) { | |
| 74 | + return e; | |
| 75 | + } | |
| 76 | + })(); | |
| 77 | + // Use either the stored mode, or resolve it | |
| 78 | + var mode = this._mode || this.mode(ex); | |
| 79 | + if (mode === 'other') { | |
| 80 | + return this.other(arguments.callee); | |
| 81 | + } else { | |
| 82 | + return this[mode](ex); | |
| 83 | + } | |
| 84 | + }, | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * @return {String} mode of operation for the environment in question. | |
| 88 | + */ | |
| 89 | + mode: function(e) { | |
| 90 | + if (e['arguments']) { | |
| 91 | + return (this._mode = 'chrome'); | |
| 92 | + } else if (window.opera && e.stacktrace) { | |
| 93 | + return (this._mode = 'opera10'); | |
| 94 | + } else if (e.stack) { | |
| 95 | + return (this._mode = 'firefox'); | |
| 96 | + } else if (window.opera && !('stacktrace' in e)) { //Opera 9- | |
| 97 | + return (this._mode = 'opera'); | |
| 98 | + } | |
| 99 | + return (this._mode = 'other'); | |
| 100 | + }, | |
| 134 | 101 | |
| 135 | - if (matches && Hoptoad.validBacktraceLine(line)) { | |
| 136 | - var file = matches[2].replace(Hoptoad.ROOT, '[PROJECT_ROOT]'); | |
| 102 | + /** | |
| 103 | + * Given a context, function name, and callback function, overwrite it so that it calls | |
| 104 | + * printStackTrace() first with a callback and then runs the rest of the body. | |
| 105 | + * | |
| 106 | + * @param {Object} context of execution (e.g. window) | |
| 107 | + * @param {String} functionName to instrument | |
| 108 | + * @param {Function} function to call with a stack trace on invocation | |
| 109 | + */ | |
| 110 | + instrumentFunction: function(context, functionName, callback) { | |
| 111 | + context = context || window; | |
| 112 | + context['_old' + functionName] = context[functionName]; | |
| 113 | + context[functionName] = function() { | |
| 114 | + callback.call(this, printStackTrace()); | |
| 115 | + return context['_old' + functionName].apply(this, arguments); | |
| 116 | + }; | |
| 117 | + context[functionName]._instrumented = true; | |
| 118 | + }, | |
| 119 | + | |
| 120 | + /** | |
| 121 | + * Given a context and function name of a function that has been | |
| 122 | + * instrumented, revert the function to it's original (non-instrumented) | |
| 123 | + * state. | |
| 124 | + * | |
| 125 | + * @param {Object} context of execution (e.g. window) | |
| 126 | + * @param {String} functionName to de-instrument | |
| 127 | + */ | |
| 128 | + deinstrumentFunction: function(context, functionName) { | |
| 129 | + if (context[functionName].constructor === Function && | |
| 130 | + context[functionName]._instrumented && | |
| 131 | + context['_old' + functionName].constructor === Function) { | |
| 132 | + context[functionName] = context['_old' + functionName]; | |
| 133 | + } | |
| 134 | + }, | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Given an Error object, return a formatted Array based on Chrome's stack string. | |
| 138 | + * | |
| 139 | + * @param e - Error object to inspect | |
| 140 | + * @return Array<String> of function calls, files and line numbers | |
| 141 | + */ | |
| 142 | + chrome: function(e) { | |
| 143 | + return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n'); | |
| 144 | + }, | |
| 137 | 145 | |
| 138 | - if (i == 0) { | |
| 139 | - if (matches[2].match(document.location.href)) { | |
| 140 | - backtrace.push('<line method="" file="internal: " number=""/>'); | |
| 141 | - } | |
| 146 | + /** | |
| 147 | + * Given an Error object, return a formatted Array based on Firefox's stack string. | |
| 148 | + * | |
| 149 | + * @param e - Error object to inspect | |
| 150 | + * @return Array<String> of function calls, files and line numbers | |
| 151 | + */ | |
| 152 | + firefox: function(e) { | |
| 153 | + return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n'); | |
| 154 | + }, | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. | |
| 158 | + * | |
| 159 | + * @param e - Error object to inspect | |
| 160 | + * @return Array<String> of function calls, files and line numbers | |
| 161 | + */ | |
| 162 | + opera10: function(e) { | |
| 163 | + var stack = e.stacktrace; | |
| 164 | + var lines = stack.split('\n'), ANON = '{anonymous}', | |
| 165 | + lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len; | |
| 166 | + for (i = 2, j = 0, len = lines.length; i < len - 2; i++) { | |
| 167 | + if (lineRE.test(lines[i])) { | |
| 168 | + var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2; | |
| 169 | + var fnName = RegExp.$3; | |
| 170 | + fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON); | |
| 171 | + lines[j++] = fnName + '@' + location; | |
| 172 | + } | |
| 173 | + } | |
| 174 | + | |
| 175 | + lines.splice(j, lines.length - j); | |
| 176 | + return lines; | |
| 177 | + }, | |
| 178 | + | |
| 179 | + // Opera 7.x-9.x only! | |
| 180 | + opera: function(e) { | |
| 181 | + var lines = e.message.split('\n'), ANON = '{anonymous}', | |
| 182 | + lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i, | |
| 183 | + i, j, len; | |
| 184 | + | |
| 185 | + for (i = 4, j = 0, len = lines.length; i < len; i += 2) { | |
| 186 | + //TODO: RegExp.exec() would probably be cleaner here | |
| 187 | + if (lineRE.test(lines[i])) { | |
| 188 | + lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, ''); | |
| 189 | + } | |
| 190 | + } | |
| 191 | + | |
| 192 | + lines.splice(j, lines.length - j); | |
| 193 | + return lines; | |
| 194 | + }, | |
| 195 | + | |
| 196 | + // Safari, IE, and others | |
| 197 | + other: function(curr) { | |
| 198 | + var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, | |
| 199 | + stack = [], fn, args, maxStackSize = 10; | |
| 200 | + | |
| 201 | + while (curr && stack.length < maxStackSize) { | |
| 202 | + fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; | |
| 203 | + args = Array.prototype.slice.call(curr['arguments']); | |
| 204 | + stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; | |
| 205 | + curr = curr.caller; | |
| 142 | 206 | } |
| 207 | + return stack; | |
| 208 | + }, | |
| 209 | + | |
| 210 | + /** | |
| 211 | + * Given arguments array as a String, subsituting type names for non-string types. | |
| 212 | + * | |
| 213 | + * @param {Arguments} object | |
| 214 | + * @return {Array} of Strings with stringified arguments | |
| 215 | + */ | |
| 216 | + stringifyArguments: function(args) { | |
| 217 | + for (var i = 0; i < args.length; ++i) { | |
| 218 | + var arg = args[i]; | |
| 219 | + if (arg === undefined) { | |
| 220 | + args[i] = 'undefined'; | |
| 221 | + } else if (arg === null) { | |
| 222 | + args[i] = 'null'; | |
| 223 | + } else if (arg.constructor) { | |
| 224 | + if (arg.constructor === Array) { | |
| 225 | + if (arg.length < 3) { | |
| 226 | + args[i] = '[' + this.stringifyArguments(arg) + ']'; | |
| 227 | + } else { | |
| 228 | + args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']'; | |
| 229 | + } | |
| 230 | + } else if (arg.constructor === Object) { | |
| 231 | + args[i] = '#object'; | |
| 232 | + } else if (arg.constructor === Function) { | |
| 233 | + args[i] = '#function'; | |
| 234 | + } else if (arg.constructor === String) { | |
| 235 | + args[i] = '"' + arg + '"'; | |
| 236 | + } | |
| 237 | + } | |
| 238 | + } | |
| 239 | + return args.join(','); | |
| 240 | + }, | |
| 241 | + | |
| 242 | + sourceCache: {}, | |
| 243 | + | |
| 244 | + /** | |
| 245 | + * @return the text from a given URL. | |
| 246 | + */ | |
| 247 | + ajax: function(url) { | |
| 248 | + var req = this.createXMLHTTPObject(); | |
| 249 | + if (!req) { | |
| 250 | + return; | |
| 251 | + } | |
| 252 | + req.open('GET', url, false); | |
| 253 | + // REMOVED FOR JS TEST. | |
| 254 | + //req.setRequestHeader('User-Agent', 'XMLHTTP/1.0'); | |
| 255 | + req.send(''); | |
| 256 | + return req.responseText; | |
| 257 | + }, | |
| 258 | + | |
| 259 | + /** | |
| 260 | + * Try XHR methods in order and store XHR factory. | |
| 261 | + * | |
| 262 | + * @return <Function> XHR function or equivalent | |
| 263 | + */ | |
| 264 | + createXMLHTTPObject: function() { | |
| 265 | + var xmlhttp, XMLHttpFactories = [ | |
| 266 | + function() { | |
| 267 | + return new XMLHttpRequest(); | |
| 268 | + }, function() { | |
| 269 | + return new ActiveXObject('Msxml2.XMLHTTP'); | |
| 270 | + }, function() { | |
| 271 | + return new ActiveXObject('Msxml3.XMLHTTP'); | |
| 272 | + }, function() { | |
| 273 | + return new ActiveXObject('Microsoft.XMLHTTP'); | |
| 274 | + } | |
| 275 | + ]; | |
| 276 | + for (var i = 0; i < XMLHttpFactories.length; i++) { | |
| 277 | + try { | |
| 278 | + xmlhttp = XMLHttpFactories[i](); | |
| 279 | + // Use memoization to cache the factory | |
| 280 | + this.createXMLHTTPObject = XMLHttpFactories[i]; | |
| 281 | + return xmlhttp; | |
| 282 | + } catch (e) {} | |
| 283 | + } | |
| 284 | + }, | |
| 143 | 285 | |
| 144 | - backtrace.push('<line method="' + Hoptoad.escapeText(matches[1]) + | |
| 145 | - '" file="' + Hoptoad.escapeText(file) + | |
| 146 | - '" number="' + matches[3] + '" />'); | |
| 147 | - } | |
| 286 | + /** | |
| 287 | + * Given a URL, check if it is in the same domain (so we can get the source | |
| 288 | + * via Ajax). | |
| 289 | + * | |
| 290 | + * @param url <String> source url | |
| 291 | + * @return False if we need a cross-domain request | |
| 292 | + */ | |
| 293 | + isSameDomain: function(url) { | |
| 294 | + return url.indexOf(location.hostname) !== -1; | |
| 295 | + }, | |
| 296 | + | |
| 297 | + /** | |
| 298 | + * Get source code from given URL if in the same domain. | |
| 299 | + * | |
| 300 | + * @param url <String> JS source URL | |
| 301 | + * @return <Array> Array of source code lines | |
| 302 | + */ | |
| 303 | + getSource: function(url) { | |
| 304 | + if (!(url in this.sourceCache)) { | |
| 305 | + this.sourceCache[url] = this.ajax(url).split('\n'); | |
| 306 | + } | |
| 307 | + return this.sourceCache[url]; | |
| 308 | + }, | |
| 309 | + | |
| 310 | + guessFunctions: function(stack) { | |
| 311 | + for (var i = 0; i < stack.length; ++i) { | |
| 312 | + var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/; | |
| 313 | + var frame = stack[i], m = reStack.exec(frame); | |
| 314 | + if (m) { | |
| 315 | + var file = m[1], lineno = m[4]; //m[7] is character position in Chrome | |
| 316 | + if (file && this.isSameDomain(file) && lineno) { | |
| 317 | + var functionName = this.guessFunctionName(file, lineno); | |
| 318 | + stack[i] = frame.replace('{anonymous}', functionName); | |
| 319 | + } | |
| 320 | + } | |
| 321 | + } | |
| 322 | + return stack; | |
| 323 | + }, | |
| 324 | + | |
| 325 | + guessFunctionName: function(url, lineNo) { | |
| 326 | + try { | |
| 327 | + return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); | |
| 328 | + } catch (e) { | |
| 329 | + return 'getSource failed with url: ' + url + ', exception: ' + e.toString(); | |
| 330 | + } | |
| 331 | + }, | |
| 332 | + | |
| 333 | + guessFunctionNameFromLines: function(lineNo, source) { | |
| 334 | + var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; | |
| 335 | + var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; | |
| 336 | + // Walk backwards from the first line in the function until we find the line which | |
| 337 | + // matches the pattern above, which is the function definition | |
| 338 | + var line = "", maxLines = 10; | |
| 339 | + for (var i = 0; i < maxLines; ++i) { | |
| 340 | + line = source[lineNo - i] + line; | |
| 341 | + if (line !== undefined) { | |
| 342 | + var m = reGuessFunction.exec(line); | |
| 343 | + if (m && m[1]) { | |
| 344 | + return m[1]; | |
| 345 | + } else { | |
| 346 | + m = reFunctionArgNames.exec(line); | |
| 347 | + if (m && m[1]) { | |
| 348 | + return m[1]; | |
| 349 | + } | |
| 350 | + } | |
| 351 | + } | |
| 352 | + } | |
| 353 | + return '(?)'; | |
| 148 | 354 | } |
| 355 | +};// Airbrake JavaScript Notifier | |
| 356 | +(function() { | |
| 357 | + "use strict"; | |
| 358 | + | |
| 359 | + var NOTICE_XML = '<?xml version="1.0" encoding="UTF-8"?>' + | |
| 360 | + '<notice version="2.0">' + | |
| 361 | + '<api-key>{key}</api-key>' + | |
| 362 | + '<notifier>' + | |
| 363 | + '<name>airbrake_js</name>' + | |
| 364 | + '<version>0.2.0</version>' + | |
| 365 | + '<url>http://airbrake.io</url>' + | |
| 366 | + '</notifier>' + | |
| 367 | + '<error>' + | |
| 368 | + '<class>{exception_class}</class>' + | |
| 369 | + '<message>{exception_message}</message>' + | |
| 370 | + '<backtrace>{backtrace_lines}</backtrace>' + | |
| 371 | + '</error>' + | |
| 372 | + '<request>' + | |
| 373 | + '<url>{request_url}</url>' + | |
| 374 | + '<component>{request_component}</component>' + | |
| 375 | + '<action>{request_action}</action>' + | |
| 376 | + '{request}' + | |
| 377 | + '</request>' + | |
| 378 | + '<server-environment>' + | |
| 379 | + '<project-root>{project_root}</project-root>' + | |
| 380 | + '<environment-name>{environment}</environment-name>' + | |
| 381 | + '</server-environment>' + | |
| 382 | + '</notice>', | |
| 383 | + REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>', | |
| 384 | + REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>', | |
| 385 | + BACKTRACE_LINE_XML = '<line method="{function}" file="{file}" number="{line}" />', | |
| 386 | + Config, | |
| 387 | + Global, | |
| 388 | + Util, | |
| 389 | + _publicAPI, | |
| 390 | + | |
| 391 | + NOTICE_JSON = { | |
| 392 | + "notifier": { | |
| 393 | + "name": "airbrake_js", | |
| 394 | + "version": "0.2.0", | |
| 395 | + "url": "http://airbrake.io" | |
| 396 | + }, | |
| 397 | + "error": [ | |
| 398 | + { | |
| 399 | + "type": "{exception_class}", | |
| 400 | + "message": "{exception_message}", | |
| 401 | + "backtrace": [] | |
| 402 | + | |
| 403 | + } | |
| 404 | + ], | |
| 405 | + "context": { | |
| 406 | + "language": "JavaScript", | |
| 407 | + "environment": "{environment}", | |
| 408 | + | |
| 409 | + "version": "1.1.1", | |
| 410 | + "url": "{request_url}", | |
| 411 | + "rootDirectory": "{project_root}", | |
| 412 | + "action": "{request_action}", | |
| 149 | 413 | |
| 150 | - return backtrace; | |
| 151 | - }, | |
| 414 | + "userId": "{}", | |
| 415 | + "userName": "{}", | |
| 416 | + "userEmail": "{}", | |
| 417 | + }, | |
| 418 | + "environment": {}, | |
| 419 | + //"session": "", | |
| 420 | + "params": {}, | |
| 421 | + }; | |
| 152 | 422 | |
| 153 | - getStackTrace: function(error) { | |
| 154 | - var stacktrace = printStackTrace({ e : error, guess : false }); | |
| 423 | + Util = { | |
| 424 | + /* | |
| 425 | + * Merge a number of objects into one. | |
| 426 | + * | |
| 427 | + * Usage example: | |
| 428 | + * var obj1 = { | |
| 429 | + * a: 'a' | |
| 430 | + * }, | |
| 431 | + * obj2 = { | |
| 432 | + * b: 'b' | |
| 433 | + * }, | |
| 434 | + * obj3 = { | |
| 435 | + * c: 'c' | |
| 436 | + * }, | |
| 437 | + * mergedObj = Util.merge(obj1, obj2, obj3); | |
| 438 | + * | |
| 439 | + * mergedObj is: { | |
| 440 | + * a: 'a', | |
| 441 | + * b: 'b', | |
| 442 | + * c: 'c' | |
| 443 | + * } | |
| 444 | + * | |
| 445 | + */ | |
| 446 | + merge: (function() { | |
| 447 | + function processProperty (key, dest, src) { | |
| 448 | + if (src.hasOwnProperty(key)) { | |
| 449 | + dest[key] = src[key]; | |
| 450 | + } | |
| 451 | + } | |
| 155 | 452 | |
| 156 | - for (var i = 0, l = stacktrace.length; i < l; i++) { | |
| 157 | - if (stacktrace[i].match(/\:\d+$/)) { | |
| 158 | - continue; | |
| 159 | - } | |
| 453 | + return function() { | |
| 454 | + var objects = Array.prototype.slice.call(arguments), | |
| 455 | + obj, | |
| 456 | + key, | |
| 457 | + result = {}; | |
| 160 | 458 | |
| 161 | - if (stacktrace[i].indexOf('@') == -1) { | |
| 162 | - stacktrace[i] += '@unsupported.js'; | |
| 163 | - } | |
| 459 | + while (obj = objects.shift()) { | |
| 460 | + for (key in obj) { | |
| 461 | + processProperty(key, result, obj); | |
| 462 | + } | |
| 463 | + } | |
| 164 | 464 | |
| 165 | - stacktrace[i] += ':0'; | |
| 166 | - } | |
| 465 | + return result; | |
| 466 | + }; | |
| 467 | + })(), | |
| 468 | + | |
| 469 | + /* | |
| 470 | + * Replace &, <, >, ', " characters with correspondent HTML entities. | |
| 471 | + */ | |
| 472 | + escape: function (text) { | |
| 473 | + return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') | |
| 474 | + .replace(/'/g, ''').replace(/"/g, '"'); | |
| 475 | + }, | |
| 476 | + | |
| 477 | + /* | |
| 478 | + * Remove leading and trailing space characters. | |
| 479 | + */ | |
| 480 | + trim: function (text) { | |
| 481 | + return text.toString().replace(/^\s+/, '').replace(/\s+$/, ''); | |
| 482 | + }, | |
| 483 | + | |
| 484 | + /* | |
| 485 | + * Fill 'text' pattern with 'data' values. | |
| 486 | + * | |
| 487 | + * e.g. Utils.substitute('<{tag}></{tag}>', {tag: 'div'}, true) will return '<div></div>' | |
| 488 | + * | |
| 489 | + * emptyForUndefinedData - a flag, if true, all matched {<name>} without data.<name> value specified will be | |
| 490 | + * replaced with empty string. | |
| 491 | + */ | |
| 492 | + substitute: function (text, data, emptyForUndefinedData) { | |
| 493 | + return text.replace(/{([\w_.-]+)}/g, function(match, key) { | |
| 494 | + return (key in data) ? data[key] : (emptyForUndefinedData ? '' : match); | |
| 495 | + }); | |
| 496 | + }, | |
| 497 | + | |
| 498 | + /* | |
| 499 | + * Perform pattern rendering for an array of data objects. | |
| 500 | + * Returns a concatenation of rendered strings of all objects in array. | |
| 501 | + */ | |
| 502 | + substituteArr: function (text, dataArr, emptyForUndefinedData) { | |
| 503 | + var _i = 0, _l = 0, | |
| 504 | + returnStr = ''; | |
| 505 | + | |
| 506 | + for (_i = 0, _l = dataArr.length; _i < _l; _i += 1) { | |
| 507 | + returnStr += this.substitute(text, dataArr[_i], emptyForUndefinedData); | |
| 508 | + } | |
| 509 | + | |
| 510 | + return returnStr; | |
| 511 | + }, | |
| 512 | + | |
| 513 | + /* | |
| 514 | + * Add hook for jQuery.fn.on function, to manualy call window.Airbrake.captureException() method | |
| 515 | + * for every exception occurred. | |
| 516 | + * | |
| 517 | + * Let function 'f' be binded as an event handler: | |
| 518 | + * | |
| 519 | + * $(window).on 'click', f | |
| 520 | + * | |
| 521 | + * If an exception is occurred inside f's body, it will be catched here | |
| 522 | + * and forwarded to captureException method. | |
| 523 | + * | |
| 524 | + * processjQueryEventHandlerWrapping is called every time window.Airbrake.setTrackJQ method is used, | |
| 525 | + * if it switches previously setted value. | |
| 526 | + */ | |
| 527 | + processjQueryEventHandlerWrapping: function () { | |
| 528 | + if (Config.options.trackJQ === true) { | |
| 529 | + Config.jQuery_fn_on_original = Config.jQuery_fn_on_original || jQuery.fn.on; | |
| 167 | 530 | |
| 168 | - return stacktrace; | |
| 169 | - }, | |
| 531 | + jQuery.fn.on = function () { | |
| 532 | + var args = Array.prototype.slice.call(arguments), | |
| 533 | + fnArgIdx = 4; | |
| 170 | 534 | |
| 171 | - validBacktraceLine: function(line) { | |
| 172 | - for (var i = 0; i < Hoptoad.backtrace_filters.length; i++) { | |
| 173 | - if (line.match(Hoptoad.backtrace_filters[i])) { | |
| 174 | - return false; | |
| 175 | - } | |
| 176 | - } | |
| 535 | + // Search index of function argument | |
| 536 | + while((--fnArgIdx > -1) && (typeof args[fnArgIdx] !== 'function')); | |
| 537 | + | |
| 538 | + // If the function is not found, then subscribe original event handler function | |
| 539 | + if (fnArgIdx === -1) { | |
| 540 | + return Config.jQuery_fn_on_original.apply(this, arguments); | |
| 541 | + } | |
| 542 | + | |
| 543 | + // If the function is found, then subscribe wrapped event handler function | |
| 544 | + args[fnArgIdx] = (function (fnOriginHandler) { | |
| 545 | + return function() { | |
| 546 | + try { | |
| 547 | + fnOriginHandler.apply(this, arguments); | |
| 548 | + } catch (e) { | |
| 549 | + Global.captureException(e); | |
| 550 | + } | |
| 551 | + }; | |
| 552 | + })(args[fnArgIdx]); | |
| 553 | + | |
| 554 | + // Call original jQuery.fn.on, with the same list of arguments, but | |
| 555 | + // a function replaced with a proxy. | |
| 556 | + return Config.jQuery_fn_on_original.apply(this, args); | |
| 557 | + }; | |
| 558 | + } else { | |
| 559 | + // Recover original jQuery.fn.on if Config.options.trackJQ is set to false | |
| 560 | + (typeof Config.jQuery_fn_on_original === 'function') && (jQuery.fn.on = Config.jQuery_fn_on_original); | |
| 561 | + } | |
| 562 | + }, | |
| 177 | 563 | |
| 178 | - return true; | |
| 179 | - }, | |
| 564 | + isjQueryPresent: function () { | |
| 565 | + // Currently only 1.7.x version supported | |
| 566 | + return (typeof jQuery === 'function') && ('fn' in jQuery) && ('jquery' in jQuery.fn) | |
| 567 | + && (jQuery.fn.jquery.indexOf('1.7') === 0) | |
| 568 | + }, | |
| 569 | + | |
| 570 | + /* | |
| 571 | + * Make first letter in a string capital. e.g. 'guessFunctionName' -> 'GuessFunctionName' | |
| 572 | + * Is used to generate getter and setter method names. | |
| 573 | + */ | |
| 574 | + capitalizeFirstLetter: function (str) { | |
| 575 | + return str[0].toUpperCase() + str.slice(1); | |
| 576 | + }, | |
| 577 | + | |
| 578 | + /* | |
| 579 | + * Generate public API from an array of specifically formated objects, e.g. | |
| 580 | + * | |
| 581 | + * - this will generate 'setEnvironment' and 'getEnvironment' API methods for configObj.xmlData.environment variable: | |
| 582 | + * { | |
| 583 | + * variable: 'environment', | |
| 584 | + * namespace: 'xmlData' | |
| 585 | + * } | |
| 586 | + * | |
| 587 | + * - this will define 'method' function as 'captureException' API method | |
| 588 | + * { | |
| 589 | + * methodName: 'captureException', | |
| 590 | + * method: (function (...) {...}); | |
| 591 | + * } | |
| 592 | + * | |
| 593 | + */ | |
| 594 | + generatePublicAPI: (function () { | |
| 595 | + function _generateSetter (variable, namespace, configObj) { | |
| 596 | + return function (value) { | |
| 597 | + configObj[namespace][variable] = value; | |
| 598 | + }; | |
| 599 | + } | |
| 600 | + | |
| 601 | + function _generateGetter (variable, namespace, configObj) { | |
| 602 | + return function (value) { | |
| 603 | + return configObj[namespace][variable]; | |
| 604 | + }; | |
| 605 | + } | |
| 606 | + | |
| 607 | + /* | |
| 608 | + * publicAPI: array of specifically formated objects | |
| 609 | + * configObj: inner configuration object | |
| 610 | + */ | |
| 611 | + return function (publicAPI, configObj) { | |
| 612 | + var _i = 0, _m = null, _capitalized = '', | |
| 613 | + returnObj = {}; | |
| 614 | + | |
| 615 | + for (_i = 0; _i < publicAPI.length; _i += 1) { | |
| 616 | + _m = publicAPI[_i]; | |
| 617 | + | |
| 618 | + switch (true) { | |
| 619 | + case (typeof _m.variable !== 'undefined') && (typeof _m.methodName === 'undefined'): | |
| 620 | + _capitalized = Util.capitalizeFirstLetter(_m.variable) | |
| 621 | + returnObj['set' + _capitalized] = _generateSetter(_m.variable, _m.namespace, configObj); | |
| 622 | + returnObj['get' + _capitalized] = _generateGetter(_m.variable, _m.namespace, configObj); | |
| 623 | + | |
| 624 | + break; | |
| 625 | + case (typeof _m.methodName !== 'undefined') && (typeof _m.method !== 'undefined'): | |
| 626 | + returnObj[_m.methodName] = _m.method | |
| 627 | + | |
| 628 | + break; | |
| 629 | + | |
| 630 | + default: | |
| 631 | + } | |
| 632 | + } | |
| 633 | + | |
| 634 | + return returnObj; | |
| 635 | + }; | |
| 636 | + } ()) | |
| 637 | + }; | |
| 638 | + | |
| 639 | + /* | |
| 640 | + * The object to store settings. Allocated from the Global (windows scope) so that users can change settings | |
| 641 | + * only through the methods, rather than through a direct change of the object fileds. So that we can to handle | |
| 642 | + * change settings event (in setter method). | |
| 643 | + */ | |
| 644 | + Config = { | |
| 645 | + xmlData: { | |
| 646 | + environment: 'environment' | |
| 647 | + }, | |
| 648 | + | |
| 649 | + options: { | |
| 650 | + trackJQ: false, // jQuery.fn.jquery | |
| 651 | + host: 'api.airbrake.io', | |
| 652 | + errorDefaults: {}, | |
| 653 | + guessFunctionName: false, | |
| 654 | + requestType: 'GET', // Can be 'POST' or 'GET' | |
| 655 | + outputFormat: 'XML' // Can be 'XML' or 'JSON' | |
| 656 | + } | |
| 657 | + }; | |
| 658 | + | |
| 659 | + /* | |
| 660 | + * The public API definition object. If no 'methodName' and 'method' values specified, | |
| 661 | + * getter and setter for 'variable' will be defined. | |
| 662 | + */ | |
| 663 | + _publicAPI = [ | |
| 664 | + { | |
| 665 | + variable: 'environment', | |
| 666 | + namespace: 'xmlData' | |
| 667 | + }, { | |
| 668 | + variable: 'key', | |
| 669 | + namespace: 'xmlData' | |
| 670 | + }, { | |
| 671 | + variable: 'host', | |
| 672 | + namespace: 'options' | |
| 673 | + },{ | |
| 674 | + variable: 'projectId', | |
| 675 | + namespace: 'options' | |
| 676 | + },{ | |
| 677 | + variable: 'errorDefaults', | |
| 678 | + namespace: 'options' | |
| 679 | + }, { | |
| 680 | + variable: 'guessFunctionName', | |
| 681 | + namespace: 'options' | |
| 682 | + }, { | |
| 683 | + variable: 'outputFormat', | |
| 684 | + namespace: 'options' | |
| 685 | + }, { | |
| 686 | + methodName: 'setTrackJQ', | |
| 687 | + variable: 'trackJQ', | |
| 688 | + namespace: 'options', | |
| 689 | + method: (function (value) { | |
| 690 | + if (!Util.isjQueryPresent()) { | |
| 691 | + throw Error('Please do not call \'Airbrake.setTrackJQ\' if jQuery does\'t present'); | |
| 692 | + } | |
| 693 | + | |
| 694 | + value = !!value; | |
| 695 | + | |
| 696 | + if (Config.options.trackJQ === value) { | |
| 697 | + return; | |
| 698 | + } | |
| 699 | + | |
| 700 | + Config.options.trackJQ = value; | |
| 701 | + | |
| 702 | + Util.processjQueryEventHandlerWrapping(); | |
| 703 | + }) | |
| 704 | + }, { | |
| 705 | + methodName: 'captureException', | |
| 706 | + method: (function (e) { | |
| 707 | + new Notifier().notify({ | |
| 708 | + message: e.message, | |
| 709 | + stack: e.stack | |
| 710 | + }); | |
| 711 | + }) | |
| 712 | + } | |
| 713 | + ]; | |
| 180 | 714 | |
| 181 | - generateVariables: function(parameters) { | |
| 182 | - var key; | |
| 183 | - var result = ''; | |
| 715 | + // Share to global scope as Airbrake ("window.Hoptoad" for backward compatibility) | |
| 716 | + Global = window.Airbrake = window.Hoptoad = Util.generatePublicAPI(_publicAPI, Config); | |
| 184 | 717 | |
| 185 | - for (key in parameters) { | |
| 186 | - result += '<var key="' + Hoptoad.escapeText(key) + '">' + | |
| 187 | - Hoptoad.escapeText(parameters[key]) + | |
| 188 | - '</var>'; | |
| 718 | + function Notifier() { | |
| 719 | + this.options = Util.merge({}, Config.options); | |
| 720 | + this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData); | |
| 189 | 721 | } |
| 722 | + | |
| 723 | + Notifier.prototype = { | |
| 724 | + constructor: Notifier, | |
| 725 | + VERSION: '0.2.0', | |
| 726 | + ROOT: window.location.protocol + '//' + window.location.host, | |
| 727 | + BACKTRACE_MATCHER: /^(.*)\@(.*)\:(\d+)$/, | |
| 728 | + backtrace_filters: [/notifier\.js/], | |
| 729 | + DEF_XML_DATA: { | |
| 730 | + request: {} | |
| 731 | + }, | |
| 190 | 732 | |
| 191 | - return result; | |
| 192 | - }, | |
| 733 | + notify: (function () { | |
| 734 | + /* | |
| 735 | + * Emit GET request via <iframe> element. | |
| 736 | + * Data is transmited as a part of query string. | |
| 737 | + */ | |
| 738 | + function _sendGETRequest (url, data) { | |
| 739 | + var request = document.createElement('iframe'); | |
| 740 | + | |
| 741 | + request.style.display = 'none'; | |
| 742 | + request.src = url + '?data=' + data; | |
| 743 | + | |
| 744 | + // When request has been sent, delete iframe | |
| 745 | + request.onload = function () { | |
| 746 | + // To avoid infinite progress indicator | |
| 747 | + setTimeout(function() { | |
| 748 | + document.body.removeChild(request); | |
| 749 | + }, 0); | |
| 750 | + }; | |
| 751 | + | |
| 752 | + document.body.appendChild(request); | |
| 753 | + } | |
| 754 | + | |
| 755 | + /* | |
| 756 | + * Cross-domain AJAX POST request. | |
| 757 | + * | |
| 758 | + * It requires a server setup as described in Cross-Origin Resource Sharing spec: | |
| 759 | + * http://www.w3.org/TR/cors/ | |
| 760 | + */ | |
| 761 | + function _sendPOSTRequest (url, data) { | |
| 762 | + var request = new XMLHttpRequest(); | |
| 763 | + request.open('POST', url, true); | |
| 764 | + request.setRequestHeader('Content-Type', 'application/json'); | |
| 765 | + request.send(data); | |
| 766 | + } | |
| 767 | + | |
| 768 | + return function (error) { | |
| 769 | + var outputData = '', | |
| 770 | + url = ''; | |
| 771 | + // | |
| 772 | + | |
| 773 | + /* | |
| 774 | + * Should be changed to url = '//' + ... | |
| 775 | + * to use the protocol of current page (http or https). Only sends 'secure' if page is secure. | |
| 776 | + * XML uses V2 API. http://collect.airbrake.io/notifier_api/v2/notices | |
| 777 | + */ | |
| 778 | + | |
| 779 | + | |
| 780 | + switch (this.options['outputFormat']) { | |
| 781 | + case 'XML': | |
| 782 | + outputData = escape(this.generateXML(this.generateDataJSON(error))); | |
| 783 | + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices'; | |
| 784 | + _sendGETRequest(url, outputData); | |
| 785 | + break; | |
| 193 | 786 | |
| 194 | - escapeText: function(text) { | |
| 195 | - return text.replace(/&/g, '&') | |
| 196 | - .replace(/</g, '<') | |
| 197 | - .replace(/>/g, '>') | |
| 198 | - .replace(/'/g, ''') | |
| 199 | - .replace(/"/g, '"'); | |
| 200 | - }, | |
| 787 | + case 'JSON': | |
| 788 | + /* | |
| 789 | + * JSON uses API V3. Needs project in URL. | |
| 790 | + * http://collect.airbrake.io/api/v3/projects/[PROJECT_ID]/notices?key=[API_KEY] | |
| 791 | + * url = window.location.protocol + '://' + this.options.host + '/api/v3/projects' + this.options.projectId + '/notices?key=' + this.options.key; | |
| 792 | + */ | |
| 793 | + outputData = JSON.stringify(this.generateJSON(this.generateDataJSON(error))); | |
| 794 | + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key; | |
| 795 | + _sendPOSTRequest(url, outputData); | |
| 796 | + break; | |
| 201 | 797 | |
| 202 | - trim: function(text) { | |
| 203 | - return text.toString().replace(/^\s+/, '').replace(/\s+$/, ''); | |
| 204 | - }, | |
| 798 | + default: | |
| 799 | + } | |
| 205 | 800 | |
| 206 | - mergeDefault: function(defaults, hash) { | |
| 207 | - var cloned = {}; | |
| 208 | - var key; | |
| 801 | + }; | |
| 802 | + } ()), | |
| 803 | + | |
| 804 | + /* | |
| 805 | + * Generate inner JSON representation of exception data that can be rendered as XML or JSON. | |
| 806 | + */ | |
| 807 | + generateDataJSON: (function () { | |
| 808 | + /* | |
| 809 | + * Generate variables array for inputObj object. | |
| 810 | + * | |
| 811 | + * e.g. | |
| 812 | + * | |
| 813 | + * _generateVariables({a: 'a'}) -> [{key: 'a', value: 'a'}] | |
| 814 | + * | |
| 815 | + */ | |
| 816 | + function _generateVariables (inputObj) { | |
| 817 | + var key = '', returnArr = []; | |
| 818 | + | |
| 819 | + for (key in inputObj) { | |
| 820 | + if (inputObj.hasOwnProperty(key)) { | |
| 821 | + returnArr.push({ | |
| 822 | + key: key, | |
| 823 | + value: inputObj[key] | |
| 824 | + }); | |
| 825 | + } | |
| 826 | + } | |
| 827 | + | |
| 828 | + return returnArr; | |
| 829 | + } | |
| 830 | + | |
| 831 | + /* | |
| 832 | + * Generate Request part of notification. | |
| 833 | + */ | |
| 834 | + function _composeRequestObj (methods, errorObj) { | |
| 835 | + var _i = 0, | |
| 836 | + returnObj = {}, | |
| 837 | + type = ''; | |
| 838 | + | |
| 839 | + for (_i = 0; _i < methods.length; _i += 1) { | |
| 840 | + type = methods[_i]; | |
| 841 | + if (typeof errorObj[type] !== 'undefined') { | |
| 842 | + returnObj[type] = _generateVariables(errorObj[type]); | |
| 843 | + } | |
| 844 | + } | |
| 845 | + | |
| 846 | + return returnObj; | |
| 847 | + } | |
| 848 | + | |
| 849 | + return function (errorWithoutDefaults) { | |
| 850 | + /* | |
| 851 | + * A constructor line: | |
| 852 | + * | |
| 853 | + * this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData); | |
| 854 | + */ | |
| 855 | + var outputData = this.xmlData, | |
| 856 | + error = Util.merge(this.options.errorDefaults, errorWithoutDefaults), | |
| 857 | + | |
| 858 | + component = error.component || '', | |
| 859 | + request_url = (error.url || '' + location.hash), | |
| 860 | + | |
| 861 | + methods = ['cgi-data', 'params', 'session'], | |
| 862 | + _outputData = null; | |
| 863 | + | |
| 864 | + _outputData = { | |
| 865 | + request_url: request_url, | |
| 866 | + request_action: (error.action || ''), | |
| 867 | + request_component: component, | |
| 868 | + request: (function () { | |
| 869 | + if (request_url || component) { | |
| 870 | + error['cgi-data'] = error['cgi-data'] || {}; | |
| 871 | + error['cgi-data'].HTTP_USER_AGENT = navigator.userAgent; | |
| 872 | + return Util.merge(outputData.request, _composeRequestObj(methods, error)); | |
| 873 | + } else { | |
| 874 | + return {} | |
| 875 | + } | |
| 876 | + } ()), | |
| 877 | + | |
| 878 | + project_root: this.ROOT, | |
| 879 | + exception_class: (error.type || 'Error'), | |
| 880 | + exception_message: (error.message || 'Unknown error.'), | |
| 881 | + backtrace_lines: this.generateBacktrace(error) | |
| 882 | + } | |
| 883 | + | |
| 884 | + outputData = Util.merge(outputData, _outputData); | |
| 885 | + | |
| 886 | + return outputData; | |
| 887 | + }; | |
| 888 | + } ()), | |
| 889 | + | |
| 890 | + /* | |
| 891 | + * Generate XML notification from inner JSON representation. | |
| 892 | + * NOTICE_XML is used as pattern. | |
| 893 | + */ | |
| 894 | + generateXML: (function () { | |
| 895 | + function _generateRequestVariableGroups (requestObj) { | |
| 896 | + var _group = '', | |
| 897 | + returnStr = ''; | |
| 898 | + | |
| 899 | + for (_group in requestObj) { | |
| 900 | + if (requestObj.hasOwnProperty(_group)) { | |
| 901 | + returnStr += Util.substitute(REQUEST_VARIABLE_GROUP_XML, { | |
| 902 | + group_name: _group, | |
| 903 | + inner_content: Util.substituteArr(REQUEST_VARIABLE_XML, requestObj[_group], true) | |
| 904 | + }, true); | |
| 905 | + } | |
| 906 | + } | |
| 907 | + | |
| 908 | + return returnStr; | |
| 909 | + } | |
| 910 | + | |
| 911 | + return function (JSONdataObj) { | |
| 912 | + JSONdataObj.request = _generateRequestVariableGroups(JSONdataObj.request); | |
| 913 | + JSONdataObj.backtrace_lines = Util.substituteArr(BACKTRACE_LINE_XML, JSONdataObj.backtrace_lines, true); | |
| 914 | + | |
| 915 | + return Util.substitute(NOTICE_XML, JSONdataObj, true); | |
| 916 | + }; | |
| 917 | + } ()), | |
| 918 | + | |
| 919 | + /* | |
| 920 | + * Generate JSON notification from inner JSON representation. | |
| 921 | + * NOTICE_JSON is used as pattern. | |
| 922 | + */ | |
| 923 | + generateJSON: function (JSONdataObj) { | |
| 924 | + // Pattern string is JSON.stringify(NOTICE_JSON) | |
| 925 | + // The rendered string is parsed back as JSON. | |
| 926 | + var outputJSON = JSON.parse(Util.substitute(JSON.stringify(NOTICE_JSON), JSONdataObj, true)); | |
| 927 | + | |
| 928 | + // REMOVED - Request from JSON. | |
| 929 | + outputJSON.request = Util.merge(outputJSON.request, JSONdataObj.request); | |
| 930 | + outputJSON.error.backtrace = JSONdataObj.backtrace_lines; | |
| 931 | + | |
| 932 | + return outputJSON; | |
| 933 | + }, | |
| 934 | + | |
| 935 | + generateBacktrace: function (error) { | |
| 936 | + var backtrace = [], | |
| 937 | + file, | |
| 938 | + i, | |
| 939 | + matches, | |
| 940 | + stacktrace; | |
| 209 | 941 | |
| 210 | - for (key in hash) { | |
| 211 | - cloned[key] = hash[key]; | |
| 212 | - } | |
| 942 | + error = error || {}; | |
| 213 | 943 | |
| 214 | - for (key in defaults) { | |
| 215 | - if (!cloned.hasOwnProperty(key)) { | |
| 216 | - cloned[key] = defaults[key]; | |
| 217 | - } | |
| 218 | - } | |
| 944 | + if (typeof error.stack !== 'string') { | |
| 945 | + try { | |
| 946 | + (0)(); | |
| 947 | + } catch (e) { | |
| 948 | + error.stack = e.stack; | |
| 949 | + } | |
| 950 | + } | |
| 219 | 951 | |
| 220 | - return cloned; | |
| 221 | - } | |
| 222 | -}; | |
| 952 | + stacktrace = this.getStackTrace(error); | |
| 223 | 953 | |
| 224 | -// From: http://stacktracejs.com/ | |
| 225 | -// | |
| 226 | -// Domain Public by Eric Wendelin http://eriwen.com/ (2008) | |
| 227 | -// Luke Smith http://lucassmith.name/ (2008) | |
| 228 | -// Loic Dachary <loic@dachary.org> (2008) | |
| 229 | -// Johan Euphrosine <proppy@aminche.com> (2008) | |
| 230 | -// Oyvind Sean Kinsey http://kinsey.no/blog (2010) | |
| 231 | -// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010) | |
| 232 | - | |
| 233 | -function printStackTrace(a){var a=a||{guess:!0},b=a.e||null,a=!!a.guess,d=new printStackTrace.implementation,b=d.run(b);return a?d.guessAnonymousFunctions(b):b}printStackTrace.implementation=function(){}; | |
| 234 | -printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);return"other"===b?this.other(arguments.callee):this[b](a)},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){return a.arguments&&a.stack?"chrome":a.stack&&a.sourceURL?"safari":"string"===typeof a.message&&"undefined"!==typeof window&&window.opera?!a.stacktrace||-1<a.message.indexOf("\n")&&a.message.split("\n").length>a.stacktrace.split("\n").length?"opera9":!a.stack? | |
| 235 | -"opera10a":0>a.stacktrace.indexOf("called from line")?"opera10b":"opera11":a.stack?"firefox":"other"},instrumentFunction:function(a,b,d){var a=a||window,c=a[b];a[b]=function(){d.call(this,printStackTrace().slice(4));return a[b]._instrumented.apply(this,arguments)};a[b]._instrumented=c},deinstrumentFunction:function(a,b){a[b].constructor===Function&&(a[b]._instrumented&&a[b]._instrumented.constructor===Function)&&(a[b]=a[b]._instrumented)},chrome:function(a){a=(a.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm, | |
| 236 | -"").replace(/^\s+(at eval )?at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},safari:function(a){return a.stack.replace(/\[native code\]\n/m,"").replace(/^@/gm,"{anonymous}()@").split("\n")},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^[\(@]/gm,"{anonymous}()@").split("\n")},opera11:function(a){for(var b=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/, | |
| 237 | -a=a.stacktrace.split("\n"),d=[],c=0,f=a.length;c<f;c+=2){var e=b.exec(a[c]);if(e){var g=e[4]+":"+e[1]+":"+e[2],e=e[3]||"global code",e=e.replace(/<anonymous function: (\S+)>/,"$1").replace(/<anonymous function>/,"{anonymous}");d.push(e+"@"+g+" -- "+a[c+1].replace(/^\s+/,""))}}return d},opera10b:function(a){for(var b=/^(.*)@(.+):(\d+)$/,a=a.stacktrace.split("\n"),d=[],c=0,f=a.length;c<f;c++){var e=b.exec(a[c]);e&&d.push((e[1]?e[1]+"()":"global code")+"@"+e[2]+":"+e[3])}return d},opera10a:function(a){for(var b= | |
| 238 | -/Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i,a=a.stacktrace.split("\n"),d=[],c=0,f=a.length;c<f;c+=2){var e=b.exec(a[c]);e&&d.push((e[3]||"{anonymous}")+"()@"+e[2]+":"+e[1]+" -- "+a[c+1].replace(/^\s+/,""))}return d},opera9:function(a){for(var b=/Line (\d+).*script (?:in )?(\S+)/i,a=a.message.split("\n"),d=[],c=2,f=a.length;c<f;c+=2){var e=b.exec(a[c]);e&&d.push("{anonymous}()@"+e[2]+":"+e[1]+" -- "+a[c+1].replace(/^\s+/,""))}return d},other:function(a){for(var b=/function\s*([\w\-$]+)?\s*\(/i, | |
| 239 | -d=[],c,f;a&&a.arguments&&10>d.length;)c=b.test(a.toString())?RegExp.$1||"{anonymous}":"{anonymous}",f=Array.prototype.slice.call(a.arguments||[]),d[d.length]=c+"("+this.stringifyArguments(f)+")",a=a.caller;return d},stringifyArguments:function(a){for(var b=[],d=Array.prototype.slice,c=0;c<a.length;++c){var f=a[c];void 0===f?b[c]="undefined":null===f?b[c]="null":f.constructor&&(f.constructor===Array?b[c]=3>f.length?"["+this.stringifyArguments(f)+"]":"["+this.stringifyArguments(d.call(f,0,1))+"..."+ | |
| 240 | -this.stringifyArguments(d.call(f,-1))+"]":f.constructor===Object?b[c]="#object":f.constructor===Function?b[c]="#function":f.constructor===String?b[c]='"'+f+'"':f.constructor===Number&&(b[c]=f))}return b.join(",")},sourceCache:{},ajax:function(a){var b=this.createXMLHTTPObject();if(b)try{return b.open("GET",a,!1),b.send(null),b.responseText}catch(d){}return""},createXMLHTTPObject:function(){for(var a,b=[function(){return new XMLHttpRequest},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")}, | |
| 241 | -function(){return new ActiveXObject("Microsoft.XMLHTTP")}],d=0;d<b.length;d++)try{return a=b[d](),this.createXMLHTTPObject=b[d],a}catch(c){}},isSameDomain:function(a){return"undefined"!==typeof location&&-1!==a.indexOf(location.hostname)},getSource:function(a){a in this.sourceCache||(this.sourceCache[a]=this.ajax(a).split("\n"));return this.sourceCache[a]},guessAnonymousFunctions:function(a){for(var b=0;b<a.length;++b){var d=/^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,c=a[b],f=/\{anonymous\}\(.*\)@(.*)/.exec(c); | |
| 242 | -if(f){var e=d.exec(f[1]);e&&(d=e[1],f=e[2],e=e[3]||0,d&&(this.isSameDomain(d)&&f)&&(d=this.guessAnonymousFunction(d,f,e),a[b]=c.replace("{anonymous}",d)))}}return a},guessAnonymousFunction:function(a,b){var d;try{d=this.findFunctionName(this.getSource(a),b)}catch(c){d="getSource failed with url: "+a+", exception: "+c.toString()}return d},findFunctionName:function(a,b){for(var d=/function\s+([^(]*?)\s*\(([^)]*)\)/,c=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/,f=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/, | |
| 243 | -e="",g,j=Math.min(b,20),h,i=0;i<j;++i)if(g=a[b-i-1],h=g.indexOf("//"),0<=h&&(g=g.substr(0,h)),g)if(e=g+e,(g=c.exec(e))&&g[1]||(g=d.exec(e))&&g[1]||(g=f.exec(e))&&g[1])return g[1];return"(?)"}}; | |
| 244 | - | |
| 245 | -window.onerror = function(message, file, line) { | |
| 246 | - setTimeout(function() { | |
| 247 | - Hoptoad.notify({ | |
| 248 | - message : message, | |
| 249 | - stack : '()@' + file + ':' + line, | |
| 250 | - url : document.location.href | |
| 251 | - }); | |
| 252 | - }, 100); | |
| 253 | - return true; | |
| 254 | -}; | |
| 954 | + for (i = 0; i < stacktrace.length; i++) { | |
| 955 | + matches = stacktrace[i].match(this.BACKTRACE_MATCHER); | |
| 956 | + | |
| 957 | + if (matches && this.validBacktraceLine(stacktrace[i])) { | |
| 958 | + file = matches[2].replace(this.ROOT, '[PROJECT_ROOT]'); | |
| 959 | + | |
| 960 | + if (i === 0 && matches[2].match(document.location.href)) { | |
| 961 | + // backtrace.push('<line method="" file="internal: " number=""/>'); | |
| 962 | + | |
| 963 | + backtrace.push({ | |
| 964 | + // Updated to fit in with V3 new terms for Backtrace data. | |
| 965 | + 'function': '', | |
| 966 | + file: 'internal: ', | |
| 967 | + line: '' | |
| 968 | + }); | |
| 969 | + } | |
| 970 | + | |
| 971 | + // backtrace.push('<line method="' + Util.escape(matches[1]) + '" file="' + Util.escape(file) + | |
| 972 | + // '" number="' + matches[3] + '" />'); | |
| 973 | + | |
| 974 | + backtrace.push({ | |
| 975 | + 'function': matches[1], | |
| 976 | + file: file, | |
| 977 | + line: matches[3] | |
| 978 | + }); | |
| 979 | + } | |
| 980 | + } | |
| 981 | + | |
| 982 | + return backtrace; | |
| 983 | + }, | |
| 984 | + | |
| 985 | + getStackTrace: function (error) { | |
| 986 | + var i, | |
| 987 | + stacktrace = printStackTrace({ | |
| 988 | + e: error, | |
| 989 | + guess: this.options.guessFunctionName | |
| 990 | + }); | |
| 991 | + | |
| 992 | + for (i = 0; i < stacktrace.length; i++) { | |
| 993 | + if (stacktrace[i].match(/\:\d+$/)) { | |
| 994 | + continue; | |
| 995 | + } | |
| 996 | + | |
| 997 | + if (stacktrace[i].indexOf('@') === -1) { | |
| 998 | + stacktrace[i] += '@unsupported.js'; | |
| 999 | + } | |
| 1000 | + | |
| 1001 | + stacktrace[i] += ':0'; | |
| 1002 | + } | |
| 1003 | + | |
| 1004 | + return stacktrace; | |
| 1005 | + }, | |
| 1006 | + | |
| 1007 | + validBacktraceLine: function (line) { | |
| 1008 | + for (var i = 0; i < this.backtrace_filters.length; i++) { | |
| 1009 | + if (line.match(this.backtrace_filters[i])) { | |
| 1010 | + return false; | |
| 1011 | + } | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + return true; | |
| 1015 | + } | |
| 1016 | + }; | |
| 1017 | + | |
| 1018 | + window.onerror = function (message, file, line) { | |
| 1019 | + setTimeout(function () { | |
| 1020 | + new Notifier().notify({ | |
| 1021 | + message: message, | |
| 1022 | + stack: '()@' + file + ':' + line | |
| 1023 | + }); | |
| 1024 | + }, 0); | |
| 255 | 1025 | |
| 1026 | + return true; | |
| 1027 | + }; | |
| 1028 | +})(); | |
| 1029 | +})(window, document); | ... | ... |