diff --git a/htdocs/includes/raven-js/.bower.json b/htdocs/includes/raven-js/.bower.json new file mode 100644 index 0000000000000000000000000000000000000000..f42bb67b949a5f55f9a295b6a94de07b802ad549 --- /dev/null +++ b/htdocs/includes/raven-js/.bower.json @@ -0,0 +1,18 @@ +{ + "name": "raven-js", + "version": "1.1.19", + "dependencies": {}, + "main": "dist/raven.js", + "ignore": {}, + "homepage": "https://github.com/getsentry/raven-js", + "_release": "1.1.19", + "_resolution": { + "type": "version", + "tag": "1.1.19", + "commit": "82b9c07b7545c6c10e297709a741eaa9b75f64e8" + }, + "_source": "git://github.com/getsentry/raven-js.git", + "_target": "~1.1.19", + "_originalSource": "raven-js", + "_direct": true +} \ No newline at end of file diff --git a/htdocs/includes/raven-js/.gitignore b/htdocs/includes/raven-js/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..02111f91fe4975277f43eb9796288d8ca6c91486 --- /dev/null +++ b/htdocs/includes/raven-js/.gitignore @@ -0,0 +1,22 @@ +.DS_Store + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +docs/html +docs/doctrees + +build +node_modules +npm-debug.log + +scratch/ + +*.pyc + +.idea +aws.json diff --git a/htdocs/includes/raven-js/.jshintrc b/htdocs/includes/raven-js/.jshintrc new file mode 100644 index 0000000000000000000000000000000000000000..6e4ec7353317f370c369aa0eb81f158af33fc324 --- /dev/null +++ b/htdocs/includes/raven-js/.jshintrc @@ -0,0 +1,9 @@ +{ + "es3": true, + "globalstrict": true, + "browser": true, + "predef": [ + "TraceKit", + "console" + ] +} diff --git a/htdocs/includes/raven-js/.travis.yml b/htdocs/includes/raven-js/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..3b4f88c1e936f6bc0502c59e3f54c073db9c9d08 --- /dev/null +++ b/htdocs/includes/raven-js/.travis.yml @@ -0,0 +1,8 @@ +# language doesn't matter, we're only using phantom.js +language: node_js +node_js: + - "0.10" +script: + - ./node_modules/.bin/grunt test build +notifications: + irc: "irc.freenode.org#sentry" diff --git a/htdocs/includes/raven-js/AUTHORS b/htdocs/includes/raven-js/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..7f1ae4232176cb8a56911cff8392908bcca37ddc --- /dev/null +++ b/htdocs/includes/raven-js/AUTHORS @@ -0,0 +1 @@ +https://github.com/getsentry/raven-js/graphs/contributors diff --git a/htdocs/includes/raven-js/Gruntfile.js b/htdocs/includes/raven-js/Gruntfile.js new file mode 100644 index 0000000000000000000000000000000000000000..fce8eb172721aa61190d53eceee4d3ef9255e2bb --- /dev/null +++ b/htdocs/includes/raven-js/Gruntfile.js @@ -0,0 +1,254 @@ +module.exports = function(grunt) { + "use strict"; + + var _ = require('lodash'); + var path = require('path'); + + var coreFiles = [ + 'template/_header.js', + 'vendor/**/*.js', + 'src/**/*.js', + 'template/_footer.js' + ]; + + var plugins = grunt.option('plugins'); + // Create plugin paths and verify hey exist + plugins = _.map(plugins ? plugins.split(',') : [], function (plugin) { + var path = 'plugins/' + plugin + '.js'; + + if(!grunt.file.exists(path)) + throw new Error("Plugin '" + plugin + "' not found in plugins directory."); + + return path; + }); + + // Taken from http://dzone.com/snippets/calculate-all-combinations + var combine = function (a) { + var fn = function (n, src, got, all) { + if (n === 0) { + all.push(got); + return; + } + + for (var j = 0; j < src.length; j++) { + fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all); + } + }; + + var all = [a]; + + for (var i = 0; i < a.length; i++) { + fn(i, a, [], all); + } + + return all; + }; + + var pluginCombinations = combine(grunt.file.expand('plugins/*.js')); + var pluginConcatFiles = _.reduce(pluginCombinations, function (dict, comb) { + var key = _.map(comb, function (plugin) { + return path.basename(plugin, '.js'); + }); + key.sort(); + + var dest = path.join('build/', key.join(','), '/raven.js'); + dict[dest] = coreFiles.concat(comb); + + return dict; + }, {}); + + var gruntConfig = { + pkg: grunt.file.readJSON('package.json'), + aws: grunt.file.exists('aws.json') ? grunt.file.readJSON('aws.json'): {}, + + clean: ['build'], + concat: { + options: { + separator: '\n', + banner: grunt.file.read('template/_copyright.js'), + process: true + }, + core: { + src: coreFiles.concat(plugins), + dest: 'build/raven.js' + }, + all: { + files: pluginConcatFiles + } + }, + + uglify: { + options: { + sourceMap: function (dest) { + return path.join(path.dirname(dest), + path.basename(dest, '.js')) + + '.map'; + }, + sourceMappingURL: function (dest) { + return path.basename(dest, '.js') + '.map'; + }, + preserveComments: 'some' + }, + dist: { + src: ['build/**/*.js'], + ext: '.min.js', + expand: true + } + }, + + fixSourceMaps: { + all: ['build/**/*.map'] + }, + + jshint: { + options: { + jshintrc: '.jshintrc' + }, + all: ['Gruntfile.js', 'src/**/*.js', 'plugins/**/*.js'] + }, + + mocha: { + all: { + options: { + mocha: { + ignoreLeaks: true, + grep: grunt.option('grep') + }, + log: true, + reporter: 'Dot', + run: true + }, + src: ['test/index.html'], + nonull: true + } + }, + + release: { + options: { + npm: false, + commitMessage: 'Release <%= version %>' + } + }, + + s3: { + options: { + key: '<%= aws.key %>', + secret: '<%= aws.secret %>', + bucket: '<%= aws.bucket %>', + access: 'public-read', + // Limit concurrency + maxOperations: 20, + headers: { + // Surrogate-Key header for Fastly to purge by release + 'x-amz-meta-surrogate-key': '<%= pkg.release %>' + } + }, + all: { + upload: [{ + src: 'build/**/*', + dest: '<%= pkg.release %>/', + rel: 'build/' + }] + } + }, + + connect: { + test: { + options: { + port: 8000, + debug: true, + keepalive: true + } + }, + + docs: { + options: { + port: 8000, + debug: true, + base: 'docs/html', + keepalive: true + } + } + }, + + copy: { + dist: { + expand: true, + flatten: true, + cwd: 'build/', + src: '**', + dest: 'dist/' + } + } + }; + + grunt.initConfig(gruntConfig); + + // Custom Grunt tasks + grunt.registerTask('version', function() { + var pkg = grunt.config.get('pkg'); + if (grunt.option('dev')) { + pkg.release = 'dev'; + pkg.version = grunt.config.get('gitinfo').local.branch.current.shortSHA; + } else { + pkg.release = pkg.version; + } + grunt.config.set('pkg', pkg); + }); + + grunt.registerMultiTask('fixSourceMaps', function () { + this.files.forEach(function (f) { + var result; + var sources = f.src.filter(function (filepath) { + if (!grunt.file.exists(filepath)) { + grunt.log.warn('Source file "' + filepath + '" not found.'); + return false; + } else { + return true; + } + }).forEach(function (filepath) { + var base = path.dirname(filepath); + var sMap = grunt.file.readJSON(filepath); + sMap.file = path.relative(base, sMap.file); + sMap.sources = _.map(sMap.sources, path.relative.bind(path, base)); + + grunt.file.write(filepath, JSON.stringify(sMap)); + // Print a success message. + grunt.log.writeln('File "' + filepath + '" fixed.'); + }); + }); + }); + + // Grunt contrib tasks + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-copy'); + + // 3rd party Grunt tasks + grunt.loadNpmTasks('grunt-mocha'); + grunt.loadNpmTasks('grunt-release'); + grunt.loadNpmTasks('grunt-s3'); + grunt.loadNpmTasks('grunt-gitinfo'); + + // Build tasks + grunt.registerTask('_prep', ['clean', 'gitinfo', 'version']); + grunt.registerTask('concat.core', ['_prep', 'concat:core']); + grunt.registerTask('concat.all', ['_prep', 'concat:all']); + grunt.registerTask('build.core', ['concat.core', 'uglify', 'fixSourceMaps']); + grunt.registerTask('build.all', ['concat.all', 'uglify', 'fixSourceMaps']); + grunt.registerTask('build', ['build.all']); + grunt.registerTask('dist', ['build.core', 'copy:dist']); + + // Test task + grunt.registerTask('test', ['jshint', 'mocha']); + + // Webserver tasks + grunt.registerTask('run:test', ['connect:test']); + grunt.registerTask('run:docs', ['connect:docs']); + + grunt.registerTask('publish', ['test', 'build.all', 's3']); + grunt.registerTask('default', ['test']); +}; diff --git a/htdocs/includes/raven-js/LICENSE b/htdocs/includes/raven-js/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2752d4c7395d4a71c7fa47919773e66a1aff5bb9 --- /dev/null +++ b/htdocs/includes/raven-js/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) 2014 Matt Robenolt and other contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/htdocs/includes/raven-js/Makefile b/htdocs/includes/raven-js/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f378b15f09dc7880a4894b39949b8afceea1d604 --- /dev/null +++ b/htdocs/includes/raven-js/Makefile @@ -0,0 +1,20 @@ +develop: update-submodules + npm install . + +update-submodules: + git submodule init + git submodule update + +docs: + cd docs; $(MAKE) html + +docs-live: + while true; do \ + sleep 2; \ + $(MAKE) docs; \ + done + +clean: + rm -rf docs/html + +.PHONY: develop update-submodules docs docs-live clean diff --git a/htdocs/includes/raven-js/README.md b/htdocs/includes/raven-js/README.md new file mode 100644 index 0000000000000000000000000000000000000000..058a44fe41fa4051b80993d6a1e2be0314ad6f6c --- /dev/null +++ b/htdocs/includes/raven-js/README.md @@ -0,0 +1,13 @@ +# Raven.js [](https://travis-ci.org/getsentry/raven-js) + +Raven.js is a tiny standalone JavaScript client for [Sentry](https://www.getsentry.com/). + +**Raven.js v1.1 requires Sentry v6.0 or later.** + +## Resources + + * [Download](http://ravenjs.com) + * [Documentation](https://raven-js.readthedocs.org) + * [Bug Tracker](https://github.com/getsentry/raven-js/issues) + * [IRC](irc://chat.freenode.net/sentry) (chat.freenode.net, #sentry) + * Follow [@mattrobenolt](https://twitter.com/mattrobenolt) on Twitter for updates diff --git a/htdocs/includes/raven-js/bower.json b/htdocs/includes/raven-js/bower.json new file mode 100644 index 0000000000000000000000000000000000000000..ba54c40ac650da67ed4867d5d2077ba023ad19b8 --- /dev/null +++ b/htdocs/includes/raven-js/bower.json @@ -0,0 +1,7 @@ +{ + "name": "raven-js", + "version": "1.1.19", + "dependencies": {}, + "main": "dist/raven.js", + "ignore": {} +} diff --git a/htdocs/includes/raven-js/dist/raven.js b/htdocs/includes/raven-js/dist/raven.js new file mode 100644 index 0000000000000000000000000000000000000000..195d002ab6540db3440a5f7bd1cd1baca64a37eb --- /dev/null +++ b/htdocs/includes/raven-js/dist/raven.js @@ -0,0 +1,1909 @@ +/*! Raven.js 1.1.19 (b51bc89) | github.com/getsentry/raven-js */ + +/* + * Includes TraceKit + * https://github.com/getsentry/TraceKit + * + * Copyright 2015 Matt Robenolt and other contributors + * Released under the BSD license + * https://github.com/getsentry/raven-js/blob/master/LICENSE + * + */ +;(function(window, undefined){ +'use strict'; + +/* + TraceKit - Cross brower stack traces - github.com/occ/TraceKit + MIT license +*/ + +var TraceKit = { + remoteFetching: false, + collectWindowErrors: true, + // 3 lines before, the offending line, 3 lines after + linesOfContext: 7 +}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + + +/** + * TraceKit.wrap: Wrap any function in a TraceKit reporter + * Example: func = TraceKit.wrap(func); + * + * @param {Function} func Function to be wrapped + * @return {Function} The wrapped func + */ +TraceKit.wrap = function traceKitWrapper(func) { + function wrapped() { + try { + return func.apply(this, arguments); + } catch (e) { + TraceKit.report(e); + throw e; + } + } + return wrapped; +}; + +/** + * TraceKit.report: cross-browser processing of unhandled exceptions + * + * Syntax: + * TraceKit.report.subscribe(function(stackInfo) { ... }) + * TraceKit.report.unsubscribe(function(stackInfo) { ... }) + * TraceKit.report(exception) + * try { ...code... } catch(ex) { TraceKit.report(ex); } + * + * Supports: + * - Firefox: full stack trace with line numbers, plus column number + * on top frame; column number is not guaranteed + * - Opera: full stack trace with line and column numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * - IE: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * + * In theory, TraceKit should work on all of the following versions: + * - IE5.5+ (only 8.0 tested) + * - Firefox 0.9+ (only 3.5+ tested) + * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require + * Exceptions Have Stacktrace to be enabled in opera:config) + * - Safari 3+ (only 4+ tested) + * - Chrome 1+ (only 5+ tested) + * - Konqueror 3.5+ (untested) + * + * Requires TraceKit.computeStackTrace. + * + * Tries to catch all unhandled exceptions and report them to the + * subscribed handlers. Please note that TraceKit.report will rethrow the + * exception. This is REQUIRED in order to get a useful stack trace in IE. + * If the exception does not reach the top of the browser, you will only + * get a stack trace from the point where TraceKit.report was called. + * + * Handlers receive a stackInfo object as described in the + * TraceKit.computeStackTrace docs. + */ +TraceKit.report = (function reportModuleWrapper() { + var handlers = [], + lastArgs = null, + lastException = null, + lastExceptionStack = null; + + /** + * Add a crash handler. + * @param {Function} handler + */ + function subscribe(handler) { + installGlobalHandler(); + handlers.push(handler); + } + + /** + * Remove a crash handler. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.<string, *>} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (hasKey(handlers, i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + var _oldOnerrorHandler, _onErrorHandlerInstalled; + + /** + * Ensures all global unhandled exceptions are recorded. + * Supported by Gecko and IE. + * @param {string} message Error message. + * @param {string} url URL of script that generated the exception. + * @param {(number|string)} lineNo The line number at which the error + * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. + */ + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); + processLastException(); + } else if (ex) { + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(ex); + notifyHandlers(stack, true); + } else { + var location = { + 'url': url, + 'line': lineNo, + 'column': colNo + }; + location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); + location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); + stack = { + 'message': message, + 'url': document.location.href, + 'stack': [location] + }; + notifyHandlers(stack, true); + } + + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } + + return false; + } + + function installGlobalHandler () + { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = window.onerror; + window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler () + { + if (!_onErrorHandlerInstalled) { + return; + } + window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + + /** + * Reports an unhandled Error to TraceKit. + * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. + */ + function report(ex, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + processLastException(); + } + } + + var stack = TraceKit.computeStackTrace(ex); + lastExceptionStack = stack; + lastException = ex; + lastArgs = args; + + // If the stack trace is incomplete, wait for 2 seconds for + // slow slow IE to see if onerror occurs or not before reporting + // this exception; otherwise, we will end up with an incomplete + // stack trace + window.setTimeout(function () { + if (lastException === ex) { + processLastException(); + } + }, (stack.incomplete ? 2000 : 0)); + + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } + } + + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; +}()); + +/** + * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript + * + * Syntax: + * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) + * Returns: + * s.name - exception name + * s.message - exception message + * s.stack[i].url - JavaScript or HTML file URL + * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) + * s.stack[i].args - arguments passed to the function, if known + * s.stack[i].line - line number, if known + * s.stack[i].column - column number, if known + * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# + * + * Supports: + * - Firefox: full stack trace with line numbers and unreliable column + * number on top frame + * - Opera 10: full stack trace with line and column numbers + * - Opera 9-: full stack trace with line numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the topmost stacktrace element + * only + * - IE: no line numbers whatsoever + * + * Tries to guess names of anonymous functions by looking for assignments + * in the source code. In IE and Safari, we have to guess source file names + * by searching for function bodies inside all page scripts. This will not + * work for scripts that are loaded cross-domain. + * Here be dragons: some function names may be guessed incorrectly, and + * duplicate functions may be mismatched. + * + * TraceKit.computeStackTrace should only be used for tracing purposes. + * Logging of unhandled exceptions should be done with TraceKit.report, + * which builds on top of TraceKit.computeStackTrace and provides better + * IE support by utilizing the window.onerror event to retrieve information + * about the top of the stack. + * + * Note: In IE and Safari, no stack trace is recorded on the Error object, + * so computeStackTrace instead walks its *own* chain of callers. + * This means that: + * * in Safari, some methods may be missing from the stack trace; + * * in IE, the topmost function in the stack trace will always be the + * caller of computeStackTrace. + * + * This is okay for tracing (because you are likely to be calling + * computeStackTrace from the function you want to be the topmost element + * of the stack trace anyway), but not okay for logging unhandled + * exceptions (because your catch block will likely be far away from the + * inner function that actually caused the exception). + * + */ +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + var debug = false, + sourceCache = {}; + + /** + * Attempts to retrieve source code via XMLHttpRequest, which is used + * to look up anonymous function names. + * @param {string} url URL of source code. + * @return {string} Source contents. + */ + function loadSource(url) { + if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. + return ''; + } + try { + var getXHR = function() { + try { + return new window.XMLHttpRequest(); + } catch (e) { + // explicitly bubble up the exception if not found + return new window.ActiveXObject('Microsoft.XMLHTTP'); + } + }; + + var request = getXHR(); + request.open('GET', url, false); + request.send(''); + return request.responseText; + } catch (e) { + return ''; + } + } + + /** + * Retrieves source code from the source code cache. + * @param {string} url URL of source code. + * @return {Array.<string>} Source contents. + */ + function getSource(url) { + if (!isString(url)) return []; + if (!hasKey(sourceCache, url)) { + // URL needs to be able to fetched within the acceptable domain. Otherwise, + // cross-domain errors will be triggered. + var source = ''; + if (url.indexOf(document.domain) !== -1) { + source = loadSource(url); + } + sourceCache[url] = source ? source.split('\n') : []; + } + + return sourceCache[url]; + } + + /** + * Tries to use an externally loaded copy of source code to determine + * the name of a function by looking at the name of the variable it was + * assigned to, if any. + * @param {string} url URL of source code. + * @param {(string|number)} lineNo Line number in source code. + * @return {string} The function name, if discoverable. + */ + function guessFunctionName(url, lineNo) { + var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, + reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, + line = '', + maxLines = 10, + source = getSource(url), + m; + + if (!source.length) { + return UNKNOWN_FUNCTION; + } + + // Walk backwards from the first line in the function until we find the line which + // matches the pattern above, which is the function definition + for (var i = 0; i < maxLines; ++i) { + line = source[lineNo - i] + line; + + if (!isUndefined(line)) { + if ((m = reGuessFunction.exec(line))) { + return m[1]; + } else if ((m = reFunctionArgNames.exec(line))) { + return m[1]; + } + } + } + + return UNKNOWN_FUNCTION; + } + + /** + * Retrieves the surrounding lines from where an exception occurred. + * @param {string} url URL of source code. + * @param {(string|number)} line Line number in source code to centre + * around for context. + * @return {?Array.<string>} Lines of source code. + */ + function gatherContext(url, line) { + var source = getSource(url); + + if (!source.length) { + return null; + } + + var context = [], + // linesBefore & linesAfter are inclusive with the offending line. + // if linesOfContext is even, there will be one extra line + // *before* the offending line. + linesBefore = Math.floor(TraceKit.linesOfContext / 2), + // Add one extra line if linesOfContext is odd + linesAfter = linesBefore + (TraceKit.linesOfContext % 2), + start = Math.max(0, line - linesBefore - 1), + end = Math.min(source.length, line + linesAfter - 1); + + line -= 1; // convert to 0-based index + + for (var i = start; i < end; ++i) { + if (!isUndefined(source[i])) { + context.push(source[i]); + } + } + + return context.length > 0 ? context : null; + } + + /** + * Escapes special characters, except for whitespace, in a string to be + * used inside a regular expression as a string literal. + * @param {string} text The string. + * @return {string} The escaped string literal. + */ + function escapeRegExp(text) { + return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); + } + + /** + * Escapes special characters in a string to be used inside a regular + * expression as a string literal. Also ensures that HTML entities will + * be matched the same as their literal friends. + * @param {string} body The string. + * @return {string} The escaped string. + */ + function escapeCodeAsRegExpForMatchingInsideHTML(body) { + return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); + } + + /** + * Determines where a code fragment occurs in the source code. + * @param {RegExp} re The function definition. + * @param {Array.<string>} urls A list of URLs to search. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceInUrls(re, urls) { + var source, m; + for (var i = 0, j = urls.length; i < j; ++i) { + // console.log('searching', urls[i]); + if ((source = getSource(urls[i])).length) { + source = source.join('\n'); + if ((m = re.exec(source))) { + // console.log('Found function in ' + urls[i]); + + return { + 'url': urls[i], + 'line': source.substring(0, m.index).split('\n').length, + 'column': m.index - source.lastIndexOf('\n', m.index) - 1 + }; + } + } + } + + // console.log('no match'); + + return null; + } + + /** + * Determines at which column a code fragment occurs on a line of the + * source code. + * @param {string} fragment The code fragment. + * @param {string} url The URL to search. + * @param {(string|number)} line The line number to examine. + * @return {?number} The column number. + */ + function findSourceInLine(fragment, url, line) { + var source = getSource(url), + re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), + m; + + line -= 1; + + if (source && source.length > line && (m = re.exec(source[line]))) { + return m.index; + } + + return null; + } + + /** + * Determines where a function was defined within the source code. + * @param {(Function|string)} func A function reference or serialized + * function definition. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceByFunctionBody(func) { + var urls = [window.location.href], + scripts = document.getElementsByTagName('script'), + body, + code = '' + func, + codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + re, + parts, + result; + + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.src) { + urls.push(script.src); + } + } + + if (!(parts = codeRE.exec(code))) { + re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); + } + + // not sure if this is really necessary, but I don’t have a test + // corpus large enough to confirm that and it was in the original. + else { + var name = parts[1] ? '\\s+' + parts[1] : '', + args = parts[2].split(',').join('\\s*,\\s*'); + + body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); + re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); + } + + // look for a normal function definition + if ((result = findSourceInUrls(re, urls))) { + return result; + } + + // look for an old-school event handler function + if ((parts = eventRE.exec(code))) { + var event = parts[1]; + body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); + + // look for a function defined in HTML as an onXXX handler + re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); + + if ((result = findSourceInUrls(re, urls[0]))) { + return result; + } + + // look for ??? + re = new RegExp(body); + + if ((result = findSourceInUrls(re, urls))) { + return result; + } + } + + return null; + } + + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // FIREFOX: + // ex.message = qq is not defined + // ex.fileName = http://... + // ex.lineNumber = 59 + // ex.columnNumber = 69 + // ex.stack = ...stack trace... (see the example below) + // ex.name = ReferenceError + // + // CHROME: + // ex.message = qq is not defined + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (!ex.stack) { + return null; + } + + var chrome = /^\s*at (.*?) ?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(.*?)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i, + lines = ex.stack.split('\n'), + stack = [], + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = gecko.exec(lines[i]))) { + element = { + 'url': parts[3], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': parts[2] ? parts[2].split(',') : '', + 'line': +parts[4], + 'column': parts[5] ? +parts[5] : null + }; + } else if ((parts = chrome.exec(lines[i]))) { + element = { + 'url': parts[2], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'line': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + + if (element.line) { + element.context = gatherContext(element.url, element.line); + } + + stack.push(element); + } + + if (!stack.length) { + return null; + } + + if (stack[0].line && !stack[0].column && reference) { + stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); + } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + stack[0].column = ex.columnNumber + 1; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Computes stack trace information from the stacktrace property. + * Opera 10 uses this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStacktraceProp(ex) { + // Access and store the stacktrace property before doing ANYTHING + // else to it because Opera is not very good at providing it + // reliably in other circumstances. + var stacktrace = ex.stacktrace; + + var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i, + lines = stacktrace.split('\n'), + stack = [], + parts; + + for (var i = 0, j = lines.length; i < j; i += 2) { + if ((parts = testRE.exec(lines[i]))) { + var element = { + 'line': +parts[1], + 'column': +parts[2], + 'func': parts[3] || parts[4], + 'args': parts[5] ? parts[5].split(',') : [], + 'url': parts[6] + }; + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + if (element.line) { + try { + element.context = gatherContext(element.url, element.line); + } catch (exc) {} + } + + if (!element.context) { + element.context = [lines[i + 1]]; + } + + stack.push(element); + } + } + + if (!stack.length) { + return null; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * NOT TESTED. + * Computes stack trace information from an error message that includes + * the stack trace. + * Opera 9 and earlier use this method if the option to show stack + * traces is turned on in opera:config. + * @param {Error} ex + * @return {?Object.<string, *>} Stack information. + */ + function computeStackTraceFromOperaMultiLineMessage(ex) { + // Opera includes a stack trace into the exception message. An example is: + // + // Statement on line 3: Undefined variable: undefinedFunc + // Backtrace: + // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz + // undefinedFunc(a); + // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy + // zzz(x, y, z); + // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx + // yyy(a, a, a); + // Line 1 of function script + // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } + // ... + + var lines = ex.message.split('\n'); + if (lines.length < 4) { + return null; + } + + var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, + lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, + lineRE3 = /^\s*Line (\d+) of function script\s*$/i, + stack = [], + scripts = document.getElementsByTagName('script'), + inlineScriptBlocks = [], + parts, + i, + len, + source; + + for (i in scripts) { + if (hasKey(scripts, i) && !scripts[i].src) { + inlineScriptBlocks.push(scripts[i]); + } + } + + for (i = 2, len = lines.length; i < len; i += 2) { + var item = null; + if ((parts = lineRE1.exec(lines[i]))) { + item = { + 'url': parts[2], + 'func': parts[3], + 'line': +parts[1] + }; + } else if ((parts = lineRE2.exec(lines[i]))) { + item = { + 'url': parts[3], + 'func': parts[4] + }; + var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block + var script = inlineScriptBlocks[parts[2] - 1]; + if (script) { + source = getSource(item.url); + if (source) { + source = source.join('\n'); + var pos = source.indexOf(script.innerText); + if (pos >= 0) { + item.line = relativeLine + source.substring(0, pos).split('\n').length; + } + } + } + } else if ((parts = lineRE3.exec(lines[i]))) { + var url = window.location.href.replace(/#.*$/, ''), + line = parts[1]; + var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1])); + source = findSourceInUrls(re, [url]); + item = { + 'url': url, + 'line': source ? source.line : line, + 'func': '' + }; + } + + if (item) { + if (!item.func) { + item.func = guessFunctionName(item.url, item.line); + } + var context = gatherContext(item.url, item.line); + var midline = (context ? context[Math.floor(context.length / 2)] : null); + if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) { + item.context = context; + } else { + // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url); + item.context = [lines[i + 1]]; + } + stack.push(item); + } + } + if (!stack.length) { + return null; // could not parse multiline exception message as Opera stack trace + } + + return { + 'name': ex.name, + 'message': lines[0], + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Adds information about the first frame to incomplete stack traces. + * Safari and IE require this to get complete data on the first frame. + * @param {Object.<string, *>} stackInfo Stack trace information from + * one of the compute* methods. + * @param {string} url The URL of the script that caused an error. + * @param {(number|string)} lineNo The line number of the script that + * caused an error. + * @param {string=} message The error generated by the browser, which + * hopefully contains the name of the object that caused the error. + * @return {boolean} Whether or not the stack information was + * augmented. + */ + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { + var initial = { + 'url': url, + 'line': lineNo + }; + + if (initial.url && initial.line) { + stackInfo.incomplete = false; + + if (!initial.func) { + initial.func = guessFunctionName(initial.url, initial.line); + } + + if (!initial.context) { + initial.context = gatherContext(initial.url, initial.line); + } + + var reference = / '([^']+)' /.exec(message); + if (reference) { + initial.column = findSourceInLine(reference[1], initial.url, initial.line); + } + + if (stackInfo.stack.length > 0) { + if (stackInfo.stack[0].url === initial.url) { + if (stackInfo.stack[0].line === initial.line) { + return false; // already in stack trace + } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) { + stackInfo.stack[0].line = initial.line; + stackInfo.stack[0].context = initial.context; + return false; + } + } + } + + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } + + return false; + } + + /** + * Computes stack trace information by walking the arguments.caller + * chain at the time the exception occurred. This will cause earlier + * frames to be missed but is the only way to get any stack trace in + * Safari and IE. The top frame is restored by + * {@link augmentStackTraceWithInitialElement}. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceByWalkingCallerChain(ex, depth) { + var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, + stack = [], + funcs = {}, + recursion = false, + parts, + item, + source; + + for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) { + if (curr === computeStackTrace || curr === TraceKit.report) { + // console.log('skipping internal function'); + continue; + } + + item = { + 'url': null, + 'func': UNKNOWN_FUNCTION, + 'line': null, + 'column': null + }; + + if (curr.name) { + item.func = curr.name; + } else if ((parts = functionName.exec(curr.toString()))) { + item.func = parts[1]; + } + + if ((source = findSourceByFunctionBody(curr))) { + item.url = source.url; + item.line = source.line; + + if (item.func === UNKNOWN_FUNCTION) { + item.func = guessFunctionName(item.url, item.line); + } + + var reference = / '([^']+)' /.exec(ex.message || ex.description); + if (reference) { + item.column = findSourceInLine(reference[1], source.url, source.line); + } + } + + if (funcs['' + curr]) { + recursion = true; + }else{ + funcs['' + curr] = true; + } + + stack.push(item); + } + + if (depth) { + // console.log('depth is ' + depth); + // console.log('stack is ' + stack.length); + stack.splice(0, depth); + } + + var result = { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); + return result; + } + + /** + * Computes a stack trace for an exception. + * @param {Error} ex + * @param {(string|number)=} depth + */ + function computeStackTrace(ex, depth) { + var stack = null; + depth = (depth == null ? 0 : +depth); + + try { + // This must be tried first because Opera 10 *destroys* + // its stacktrace property if you try to access the stack + // property first!! + stack = computeStackTraceFromStacktraceProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromOperaMultiLineMessage(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + return {}; + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; + computeStackTrace.guessFunctionName = guessFunctionName; + computeStackTrace.gatherContext = gatherContext; + + return computeStackTrace; +}()); + +'use strict'; + +// First, check for JSON support +// If there is no JSON, we no-op the core features of Raven +// since JSON is required to encode the payload +var _Raven = window.Raven, + hasJSON = !!(typeof JSON === 'object' && JSON.stringify), + lastCapturedException, + lastEventId, + globalServer, + globalUser, + globalKey, + globalProject, + globalOptions = { + logger: 'javascript', + ignoreErrors: [], + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + collectWindowErrors: true, + tags: {}, + maxMessageLength: 100, + extra: {} + }, + authQueryString, + isRavenInstalled = false, + + objectPrototype = Object.prototype, + startTime = now(); + +/* + * The core Raven singleton + * + * @this {Raven} + */ +var Raven = { + VERSION: '1.1.19', + + debug: true, + + /* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ + noConflict: function() { + window.Raven = _Raven; + return Raven; + }, + + /* + * Configure Raven with a DSN and extra options + * + * @param {string} dsn The public Sentry DSN + * @param {object} options Optional set of of global options [optional] + * @return {Raven} + */ + config: function(dsn, options) { + if (globalServer) { + logDebug('error', 'Error: Raven has already been configured'); + return Raven; + } + if (!dsn) return Raven; + + var uri = parseDSN(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); + + // merge in options + if (options) { + each(options, function(key, value){ + globalOptions[key] = value; + }); + } + + // "Script error." is hard coded into browsers for errors that it can't read. + // this is the result of a script being pulled in from an external domain and CORS. + globalOptions.ignoreErrors.push(/^Script error\.?$/); + globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/); + + // join regexp rules into one big rule + globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors); + globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false; + globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false; + globalOptions.includePaths = joinRegExp(globalOptions.includePaths); + + globalKey = uri.user; + globalProject = uri.path.substr(lastSlash + 1); + + // assemble the endpoint from the uri pieces + globalServer = '//' + uri.host + + (uri.port ? ':' + uri.port : '') + + '/' + path + 'api/' + globalProject + '/store/'; + + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + + if (globalOptions.fetchContext) { + TraceKit.remoteFetching = true; + } + + if (globalOptions.linesOfContext) { + TraceKit.linesOfContext = globalOptions.linesOfContext; + } + + TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; + + setAuthQueryString(); + + // return for chaining + return Raven; + }, + + /* + * Installs a global window.onerror error handler + * to capture and report uncaught exceptions. + * At this point, install() is required to be called due + * to the way TraceKit is set up. + * + * @return {Raven} + */ + install: function() { + if (isSetup() && !isRavenInstalled) { + TraceKit.report.subscribe(handleStackInfo); + isRavenInstalled = true; + } + + return Raven; + }, + + /* + * Wrap code within a context so Raven can capture errors + * reliably across domains that is executed immediately. + * + * @param {object} options A specific set of options for this context [optional] + * @param {function} func The callback to be immediately executed within the context + * @param {array} args An array of arguments to be called with the callback [optional] + */ + context: function(options, func, args) { + if (isFunction(options)) { + args = func || []; + func = options; + options = undefined; + } + + return Raven.wrap(options, func).apply(this, args); + }, + + /* + * Wrap code within a context and returns back a new function to be executed + * + * @param {object} options A specific set of options for this context [optional] + * @param {function} func The function to be wrapped in a new context + * @return {function} The newly wrapped functions with a context + */ + wrap: function(options, func) { + // 1 argument has been passed, and it's not a function + // so just return it + if (isUndefined(func) && !isFunction(options)) { + return options; + } + + // options is optional + if (isFunction(options)) { + func = options; + options = undefined; + } + + // At this point, we've passed along 2 arguments, and the second one + // is not a function either, so we'll just return the second argument. + if (!isFunction(func)) { + return func; + } + + // We don't wanna wrap it twice! + if (func.__raven__) { + return func; + } + + function wrapped() { + var args = [], i = arguments.length, + deep = !options || options && options.deep !== false; + // Recursively wrap all of a function's arguments that are + // functions themselves. + + while(i--) args[i] = deep ? Raven.wrap(options, arguments[i]) : arguments[i]; + + try { + /*jshint -W040*/ + return func.apply(this, args); + } catch(e) { + Raven.captureException(e, options); + throw e; + } + } + + // copy over properties of the old function + for (var property in func) { + if (hasKey(func, property)) { + wrapped[property] = func[property]; + } + } + + // Signal that this function has been wrapped already + // for both debugging and to prevent it to being wrapped twice + wrapped.__raven__ = true; + wrapped.__inner__ = func; + + return wrapped; + }, + + /* + * Uninstalls the global error handler. + * + * @return {Raven} + */ + uninstall: function() { + TraceKit.report.uninstall(); + isRavenInstalled = false; + + return Raven; + }, + + /* + * Manually capture an exception and send it over to Sentry + * + * @param {error} ex An exception to be logged + * @param {object} options A specific set of options for this error [optional] + * @return {Raven} + */ + captureException: function(ex, options) { + // If not an Error is passed through, recall as a message instead + if (!isError(ex)) return Raven.captureMessage(ex, options); + + // Store the raw exception object for potential debugging and introspection + lastCapturedException = ex; + + // TraceKit.report will re-raise any exception passed to it, + // which means you have to wrap it in try/catch. Instead, we + // can wrap it here and only re-raise if TraceKit.report + // raises an exception different from the one we asked to + // report on. + try { + TraceKit.report(ex, options); + } catch(ex1) { + if(ex !== ex1) { + throw ex1; + } + } + + return Raven; + }, + + /* + * Manually send a message to Sentry + * + * @param {string} msg A plain message to be captured in Sentry + * @param {object} options A specific set of options for this message [optional] + * @return {Raven} + */ + captureMessage: function(msg, options) { + // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an + // early call; we'll error on the side of logging anything called before configuration since it's + // probably something you should see: + if (!!globalOptions.ignoreErrors.test && globalOptions.ignoreErrors.test(msg)) { + return; + } + + // Fire away! + send( + objectMerge({ + message: msg + '' // Make sure it's actually a string + }, options) + ); + + return Raven; + }, + + /* + * Set/clear a user to be sent along with the payload. + * + * @param {object} user An object representing user data [optional] + * @return {Raven} + */ + setUserContext: function(user) { + globalUser = user; + + return Raven; + }, + + /* + * Set extra attributes to be sent along with the payload. + * + * @param {object} extra An object representing extra data [optional] + * @return {Raven} + */ + setExtraContext: function(extra) { + globalOptions.extra = extra || {}; + + return Raven; + }, + + /* + * Set tags to be sent along with the payload. + * + * @param {object} tags An object representing tags [optional] + * @return {Raven} + */ + setTagsContext: function(tags) { + globalOptions.tags = tags || {}; + + return Raven; + }, + + /* + * Set release version of application + * + * @param {string} release Typically something like a git SHA to identify version + * @return {Raven} + */ + setReleaseContext: function(release) { + globalOptions.release = release; + + return Raven; + }, + + /* + * Set the dataCallback option + * + * @param {function} callback The callback to run which allows the + * data blob to be mutated before sending + * @return {Raven} + */ + setDataCallback: function(callback) { + globalOptions.dataCallback = callback; + + return Raven; + }, + + /* + * Set the shouldSendCallback option + * + * @param {function} callback The callback to run which allows + * introspecting the blob before sending + * @return {Raven} + */ + setShouldSendCallback: function(callback) { + globalOptions.shouldSendCallback = callback; + + return Raven; + }, + + /* + * Get the latest raw exception that was captured by Raven. + * + * @return {error} + */ + lastException: function() { + return lastCapturedException; + }, + + /* + * Get the last event id + * + * @return {string} + */ + lastEventId: function() { + return lastEventId; + }, + + /* + * Determine if Raven is setup and ready to go. + * + * @return {boolean} + */ + isSetup: function() { + return isSetup(); + } +}; + +Raven.setUser = Raven.setUserContext; // To be deprecated + +function triggerEvent(eventType, options) { + var event, key; + + options = options || {}; + + eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventType, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventType; + } + + for (key in options) if (hasKey(options, key)) { + event[key] = options[key]; + } + + if (document.createEvent) { + // IE9 if standards + document.dispatchEvent(event); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + document.fireEvent('on' + event.eventType.toLowerCase(), event); + } catch(e) {} + } +} + +var dsnKeys = 'source protocol user pass host port path'.split(' '), + dsnPattern = /^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/; + +function RavenConfigError(message) { + this.name = 'RavenConfigError'; + this.message = message; +} +RavenConfigError.prototype = new Error(); +RavenConfigError.prototype.constructor = RavenConfigError; + +/**** Private functions ****/ +function parseDSN(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; + + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch(e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } + + if (dsn.pass) + throw new RavenConfigError('Do not specify your private key in the DSN!'); + + return dsn; +} + +function isUndefined(what) { + return what === void 0; +} + +function isFunction(what) { + return typeof what === 'function'; +} + +function isString(what) { + return objectPrototype.toString.call(what) === '[object String]'; +} + +function isObject(what) { + return typeof what === 'object' && what !== null; +} + +function isEmptyObject(what) { + for (var k in what) return false; + return true; +} + +// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 +// with some tiny modifications +function isError(what) { + return isObject(what) && + objectPrototype.toString.call(what) === '[object Error]' || + what instanceof Error; +} + +/** + * hasKey, a better form of hasOwnProperty + * Example: hasKey(MainHostObject, property) === true/false + * + * @param {Object} host object to check property + * @param {string} key to check + */ +function hasKey(object, key) { + return objectPrototype.hasOwnProperty.call(object, key); +} + +function each(obj, callback) { + var i, j; + + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } +} + + +function setAuthQueryString() { + authQueryString = + '?sentry_version=4' + + '&sentry_client=raven-js/' + Raven.VERSION + + '&sentry_key=' + globalKey; +} + + +function handleStackInfo(stackInfo, options) { + var frames = []; + + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = normalizeFrame(stack); + if (frame) { + frames.push(frame); + } + }); + } + + triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + + processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); +} + +function normalizeFrame(frame) { + if (!frame.url) return; + + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + 'function': frame.func || '?' + }, context = extractContextFromFrame(frame), i; + + if (context) { + var keys = ['pre_context', 'context_line', 'post_context']; + i = 3; + while (i--) normalized[keys[i]] = context[i]; + } + + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !globalOptions.includePaths.test(normalized.filename) || + // Now we check for fun, if the function name is Raven or TraceKit + /(Raven|TraceKit)\./.test(normalized['function']) || + // finally, we do a last ditch effort and check for raven.min.js + /raven\.(min\.)?js$/.test(normalized.filename) + ); + + return normalized; +} + +function extractContextFromFrame(frame) { + // immediately check if we should even attempt to parse a context + if (!frame.context || !globalOptions.fetchContext) return; + + var context = frame.context, + pivot = ~~(context.length / 2), + i = context.length, isMinified = false; + + while (i--) { + // We're making a guess to see if the source is minified or not. + // To do that, we make the assumption if *any* of the lines passed + // in are greater than 300 characters long, we bail. + // Sentry will see that there isn't a context + if (context[i].length > 300) { + isMinified = true; + break; + } + } + + if (isMinified) { + // The source is minified and we don't know which column. Fuck it. + if (isUndefined(frame.column)) return; + + // If the source is minified and has a frame column + // we take a chunk of the offending line to hopefully shed some light + return [ + [], // no pre_context + context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column + [] // no post_context + ]; + } + + return [ + context.slice(0, pivot), // pre_context + context[pivot], // context_line + context.slice(pivot + 1) // post_context + ]; +} + +function processException(type, message, fileurl, lineno, frames, options) { + var stacktrace, label, i; + + // In some instances message is not actually a string, no idea why, + // so we want to always coerce it to one. + message += ''; + + // Sometimes an exception is getting logged in Sentry as + // <no message value> + // This can only mean that the message was falsey since this value + // is hardcoded into Sentry itself. + // At this point, if the message is falsey, we bail since it's useless + if (type === 'Error' && !message) return; + + if (globalOptions.ignoreErrors.test(message)) return; + + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); + stacktrace = {frames: frames}; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] + }; + } + + // Truncate the message to a max of characters + message = truncate(message, globalOptions.maxMessageLength); + + if (globalOptions.ignoreUrls && globalOptions.ignoreUrls.test(fileurl)) return; + if (globalOptions.whitelistUrls && !globalOptions.whitelistUrls.test(fileurl)) return; + + label = lineno ? message + ' at ' + lineno : message; + + // Fire away! + send( + objectMerge({ + // sentry.interfaces.Exception + exception: { + type: type, + value: message + }, + // sentry.interfaces.Stacktrace + stacktrace: stacktrace, + culprit: fileurl, + message: label + }, options) + ); +} + +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value){ + obj1[key] = value; + }); + return obj1; +} + +function truncate(str, max) { + return str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + +function now() { + return +new Date(); +} + +function getHttpData() { + var http = { + url: document.location.href, + headers: { + 'User-Agent': navigator.userAgent + } + }; + + if (document.referrer) { + http.headers.Referer = document.referrer; + } + + return http; +} + +function send(data) { + if (!isSetup()) return; + + data = objectMerge({ + project: globalProject, + logger: globalOptions.logger, + platform: 'javascript', + // sentry.interfaces.Http + request: getHttpData() + }, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(objectMerge({}, globalOptions.tags), data.tags); + data.extra = objectMerge(objectMerge({}, globalOptions.extra), data.extra); + + // Send along our own collected metadata with extra + data.extra = objectMerge({ + 'session:duration': now() - startTime + }, data.extra); + + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; + + if (globalUser) { + // sentry.interfaces.User + data.user = globalUser; + } + + // Include the release iff it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; + + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } + + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } + + // Check if the request should be filtered or not + if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { + return; + } + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + lastEventId = data.event_id || (data.event_id = uuid4()); + + makeRequest(data); +} + + +function makeRequest(data) { + var img = newImage(), + src = globalServer + authQueryString + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); + + img.crossOrigin = 'anonymous'; + img.onload = function success() { + triggerEvent('success', { + data: data, + src: src + }); + }; + img.onerror = img.onabort = function failure() { + triggerEvent('failure', { + data: data, + src: src + }); + }; + img.src = src; +} + +// Note: this is shitty, but I can't figure out how to get +// sinon to stub document.createElement without breaking everything +// so this wrapper is just so I can stub it for tests. +function newImage() { + return document.createElement('img'); +} + +function isSetup() { + if (!hasJSON) return false; // needs JSON support + if (!globalServer) { + logDebug('error', 'Error: Raven has not been configured.'); + return false; + } + return true; +} + +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 +function uuid4() { + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, + v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); +} + +function logDebug(level, message) { + if (window.console && console[level] && Raven.debug) { + console[level](message); + } +} + +function afterLoad() { + // Attempt to initialize Raven on load + var RavenConfig = window.RavenConfig; + if (RavenConfig) { + Raven.config(RavenConfig.dsn, RavenConfig.config).install(); + } +} +afterLoad(); + +// Expose Raven to the world +if (typeof define === 'function' && define.amd) { + // AMD + window.Raven = Raven; + define('raven', [], function() { + return Raven; + }); +} else if (typeof module === 'object') { + // browserify + module.exports = Raven; +} else if (typeof exports === 'object') { + // CommonJS + exports = Raven; +} else { + // Everything else + window.Raven = Raven; +} + +})(typeof window !== 'undefined' ? window : this); diff --git a/htdocs/includes/raven-js/dist/raven.min.js b/htdocs/includes/raven-js/dist/raven.min.js new file mode 100644 index 0000000000000000000000000000000000000000..ae84961d5dcf3031df5c8220ccc076ac6e654bbb --- /dev/null +++ b/htdocs/includes/raven-js/dist/raven.min.js @@ -0,0 +1,3 @@ +/*! Raven.js 1.1.19 (b51bc89) | github.com/getsentry/raven-js */ +!function(a,b){"use strict";function c(a,b){var c,d;b=b||{},a="raven"+a.substr(0,1).toUpperCase()+a.substr(1),document.createEvent?(c=document.createEvent("HTMLEvents"),c.initEvent(a,!0,!0)):(c=document.createEventObject(),c.eventType=a);for(d in b)l(b,d)&&(c[d]=b[d]);if(document.createEvent)document.dispatchEvent(c);else try{document.fireEvent("on"+c.eventType.toLowerCase(),c)}catch(e){}}function d(a){this.name="RavenConfigError",this.message=a}function e(a){var b=W.exec(a),c={},e=7;try{for(;e--;)c[V[e]]=b[e]||""}catch(f){throw new d("Invalid DSN: "+a)}if(c.pass)throw new d("Do not specify your private key in the DSN!");return c}function f(a){return void 0===a}function g(a){return"function"==typeof a}function h(a){return"[object String]"===S.toString.call(a)}function i(a){return"object"==typeof a&&null!==a}function j(a){for(var b in a)return!1;return!0}function k(a){return i(a)&&"[object Error]"===S.toString.call(a)||a instanceof Error}function l(a,b){return S.hasOwnProperty.call(a,b)}function m(a,b){var c,d;if(f(a.length))for(c in a)l(a,c)&&b.call(null,c,a[c]);else if(d=a.length)for(c=0;d>c;c++)b.call(null,c,a[c])}function n(){N="?sentry_version=4&sentry_client=raven-js/"+U.VERSION+"&sentry_key="+L}function o(a,b){var d=[];a.stack&&a.stack.length&&m(a.stack,function(a,b){var c=p(b);c&&d.push(c)}),c("handle",{stackInfo:a,options:b}),r(a.name,a.message,a.url,a.lineno,d,b)}function p(a){if(a.url){var b,c={filename:a.url,lineno:a.line,colno:a.column,"function":a.func||"?"},d=q(a);if(d){var e=["pre_context","context_line","post_context"];for(b=3;b--;)c[e[b]]=d[b]}return c.in_app=!(!Q.includePaths.test(c.filename)||/(Raven|TraceKit)\./.test(c["function"])||/raven\.(min\.)?js$/.test(c.filename)),c}}function q(a){if(a.context&&Q.fetchContext){for(var b=a.context,c=~~(b.length/2),d=b.length,e=!1;d--;)if(b[d].length>300){e=!0;break}if(e){if(f(a.column))return;return[[],b[c].substr(a.column,50),[]]}return[b.slice(0,c),b[c],b.slice(c+1)]}}function r(a,b,c,d,e,f){var g,h;b+="",("Error"!==a||b)&&(Q.ignoreErrors.test(b)||(e&&e.length?(c=e[0].filename||c,e.reverse(),g={frames:e}):c&&(g={frames:[{filename:c,lineno:d,in_app:!0}]}),b=t(b,Q.maxMessageLength),Q.ignoreUrls&&Q.ignoreUrls.test(c)||(!Q.whitelistUrls||Q.whitelistUrls.test(c))&&(h=d?b+" at "+d:b,w(s({exception:{type:a,value:b},stacktrace:g,culprit:c,message:h},f)))))}function s(a,b){return b?(m(b,function(b,c){a[b]=c}),a):a}function t(a,b){return a.length<=b?a:a.substr(0,b)+"…"}function u(){return+new Date}function v(){var a={url:document.location.href,headers:{"User-Agent":navigator.userAgent}};return document.referrer&&(a.headers.Referer=document.referrer),a}function w(a){z()&&(a=s({project:M,logger:Q.logger,platform:"javascript",request:v()},a),a.tags=s(s({},Q.tags),a.tags),a.extra=s(s({},Q.extra),a.extra),a.extra=s({"session:duration":u()-T},a.extra),j(a.tags)&&delete a.tags,K&&(a.user=K),Q.release&&(a.release=Q.release),g(Q.dataCallback)&&(a=Q.dataCallback(a)||a),a&&!j(a)&&(!g(Q.shouldSendCallback)||Q.shouldSendCallback(a))&&(I=a.event_id||(a.event_id=B()),x(a)))}function x(a){var b=y(),d=J+N+"&sentry_data="+encodeURIComponent(JSON.stringify(a));b.crossOrigin="anonymous",b.onload=function(){c("success",{data:a,src:d})},b.onerror=b.onabort=function(){c("failure",{data:a,src:d})},b.src=d}function y(){return document.createElement("img")}function z(){return P?J?!0:(C("error","Error: Raven has not been configured."),!1):!1}function A(a){for(var b,c=[],d=0,e=a.length;e>d;d++)b=a[d],h(b)?c.push(b.replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")):b&&b.source&&c.push(b.source);return new RegExp(c.join("|"),"i")}function B(){return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"==a?b:3&b|8;return c.toString(16)})}function C(b,c){a.console&&console[b]&&U.debug&&console[b](c)}function D(){var b=a.RavenConfig;b&&U.config(b.dsn,b.config).install()}var E={remoteFetching:!1,collectWindowErrors:!0,linesOfContext:7},F=[].slice,G="?";E.wrap=function(a){function b(){try{return a.apply(this,arguments)}catch(b){throw E.report(b),b}}return b},E.report=function(){function c(a){h(),o.push(a)}function d(a){for(var b=o.length-1;b>=0;--b)o[b]===a&&o.splice(b,1)}function e(){i(),o=[]}function f(a,b){var c=null;if(!b||E.collectWindowErrors){for(var d in o)if(l(o,d))try{o[d].apply(null,[a].concat(F.call(arguments,2)))}catch(e){c=e}if(c)throw c}}function g(a,b,c,d,e){var g=null;if(r)E.computeStackTrace.augmentStackTraceWithInitialElement(r,b,c,a),j();else if(e)g=E.computeStackTrace(e),f(g,!0);else{var h={url:b,line:c,column:d};h.func=E.computeStackTrace.guessFunctionName(h.url,h.line),h.context=E.computeStackTrace.gatherContext(h.url,h.line),g={message:a,url:document.location.href,stack:[h]},f(g,!0)}return m?m.apply(this,arguments):!1}function h(){n||(m=a.onerror,a.onerror=g,n=!0)}function i(){n&&(a.onerror=m,n=!1,m=b)}function j(){var a=r,b=p;p=null,r=null,q=null,f.apply(null,[a,!1].concat(b))}function k(b,c){var d=F.call(arguments,1);if(r){if(q===b)return;j()}var e=E.computeStackTrace(b);if(r=e,q=b,p=d,a.setTimeout(function(){q===b&&j()},e.incomplete?2e3:0),c!==!1)throw b}var m,n,o=[],p=null,q=null,r=null;return k.subscribe=c,k.unsubscribe=d,k.uninstall=e,k}(),E.computeStackTrace=function(){function b(b){if(!E.remoteFetching)return"";try{var c=function(){try{return new a.XMLHttpRequest}catch(b){return new a.ActiveXObject("Microsoft.XMLHTTP")}},d=c();return d.open("GET",b,!1),d.send(""),d.responseText}catch(e){return""}}function c(a){if(!h(a))return[];if(!l(u,a)){var c="";-1!==a.indexOf(document.domain)&&(c=b(a)),u[a]=c?c.split("\n"):[]}return u[a]}function d(a,b){var d,e=/function ([^(]*)\(([^)]*)\)/,g=/['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,h="",i=10,j=c(a);if(!j.length)return G;for(var k=0;i>k;++k)if(h=j[b-k]+h,!f(h)){if(d=g.exec(h))return d[1];if(d=e.exec(h))return d[1]}return G}function e(a,b){var d=c(a);if(!d.length)return null;var e=[],g=Math.floor(E.linesOfContext/2),h=g+E.linesOfContext%2,i=Math.max(0,b-g-1),j=Math.min(d.length,b+h-1);b-=1;for(var k=i;j>k;++k)f(d[k])||e.push(d[k]);return e.length>0?e:null}function g(a){return a.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g,"\\$&")}function i(a){return g(a).replace("<","(?:<|<)").replace(">","(?:>|>)").replace("&","(?:&|&)").replace('"','(?:"|")').replace(/\s+/g,"\\s+")}function j(a,b){for(var d,e,f=0,g=b.length;g>f;++f)if((d=c(b[f])).length&&(d=d.join("\n"),e=a.exec(d)))return{url:b[f],line:d.substring(0,e.index).split("\n").length,column:e.index-d.lastIndexOf("\n",e.index)-1};return null}function k(a,b,d){var e,f=c(b),h=new RegExp("\\b"+g(a)+"\\b");return d-=1,f&&f.length>d&&(e=h.exec(f[d]))?e.index:null}function m(b){for(var c,d,e,f,h=[a.location.href],k=document.getElementsByTagName("script"),l=""+b,m=/^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,n=/^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,o=0;o<k.length;++o){var p=k[o];p.src&&h.push(p.src)}if(e=m.exec(l)){var q=e[1]?"\\s+"+e[1]:"",r=e[2].split(",").join("\\s*,\\s*");c=g(e[3]).replace(/;$/,";?"),d=new RegExp("function"+q+"\\s*\\(\\s*"+r+"\\s*\\)\\s*{\\s*"+c+"\\s*}")}else d=new RegExp(g(l).replace(/\s+/g,"\\s+"));if(f=j(d,h))return f;if(e=n.exec(l)){var s=e[1];if(c=i(e[2]),d=new RegExp("on"+s+"=[\\'\"]\\s*"+c+"\\s*[\\'\"]","i"),f=j(d,h[0]))return f;if(d=new RegExp(c),f=j(d,h))return f}return null}function n(a){if(!a.stack)return null;for(var b,c,g=/^\s*at (.*?) ?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i,h=/^\s*(.*?)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i,i=a.stack.split("\n"),j=[],l=/^(.*) is undefined$/.exec(a.message),m=0,n=i.length;n>m;++m){if(b=h.exec(i[m]))c={url:b[3],func:b[1]||G,args:b[2]?b[2].split(","):"",line:+b[4],column:b[5]?+b[5]:null};else{if(!(b=g.exec(i[m])))continue;c={url:b[2],func:b[1]||G,line:+b[3],column:b[4]?+b[4]:null}}!c.func&&c.line&&(c.func=d(c.url,c.line)),c.line&&(c.context=e(c.url,c.line)),j.push(c)}return j.length?(j[0].line&&!j[0].column&&l?j[0].column=k(l[1],j[0].url,j[0].line):j[0].column||f(a.columnNumber)||(j[0].column=a.columnNumber+1),{name:a.name,message:a.message,url:document.location.href,stack:j}):null}function o(a){for(var b,c=a.stacktrace,f=/ line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,g=c.split("\n"),h=[],i=0,j=g.length;j>i;i+=2)if(b=f.exec(g[i])){var k={line:+b[1],column:+b[2],func:b[3]||b[4],args:b[5]?b[5].split(","):[],url:b[6]};if(!k.func&&k.line&&(k.func=d(k.url,k.line)),k.line)try{k.context=e(k.url,k.line)}catch(l){}k.context||(k.context=[g[i+1]]),h.push(k)}return h.length?{name:a.name,message:a.message,url:document.location.href,stack:h}:null}function p(b){var f=b.message.split("\n");if(f.length<4)return null;var g,h,k,m,n=/^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,o=/^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,p=/^\s*Line (\d+) of function script\s*$/i,q=[],r=document.getElementsByTagName("script"),s=[];for(h in r)l(r,h)&&!r[h].src&&s.push(r[h]);for(h=2,k=f.length;k>h;h+=2){var t=null;if(g=n.exec(f[h]))t={url:g[2],func:g[3],line:+g[1]};else if(g=o.exec(f[h])){t={url:g[3],func:g[4]};var u=+g[1],v=s[g[2]-1];if(v&&(m=c(t.url))){m=m.join("\n");var w=m.indexOf(v.innerText);w>=0&&(t.line=u+m.substring(0,w).split("\n").length)}}else if(g=p.exec(f[h])){var x=a.location.href.replace(/#.*$/,""),y=g[1],z=new RegExp(i(f[h+1]));m=j(z,[x]),t={url:x,line:m?m.line:y,func:""}}if(t){t.func||(t.func=d(t.url,t.line));var A=e(t.url,t.line),B=A?A[Math.floor(A.length/2)]:null;A&&B.replace(/^\s*/,"")===f[h+1].replace(/^\s*/,"")?t.context=A:t.context=[f[h+1]],q.push(t)}}return q.length?{name:b.name,message:f[0],url:document.location.href,stack:q}:null}function q(a,b,c,f){var g={url:b,line:c};if(g.url&&g.line){a.incomplete=!1,g.func||(g.func=d(g.url,g.line)),g.context||(g.context=e(g.url,g.line));var h=/ '([^']+)' /.exec(f);if(h&&(g.column=k(h[1],g.url,g.line)),a.stack.length>0&&a.stack[0].url===g.url){if(a.stack[0].line===g.line)return!1;if(!a.stack[0].line&&a.stack[0].func===g.func)return a.stack[0].line=g.line,a.stack[0].context=g.context,!1}return a.stack.unshift(g),a.partial=!0,!0}return a.incomplete=!0,!1}function r(a,b){for(var c,e,f,g=/function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,h=[],i={},j=!1,l=r.caller;l&&!j;l=l.caller)if(l!==s&&l!==E.report){if(e={url:null,func:G,line:null,column:null},l.name?e.func=l.name:(c=g.exec(l.toString()))&&(e.func=c[1]),f=m(l)){e.url=f.url,e.line=f.line,e.func===G&&(e.func=d(e.url,e.line));var n=/ '([^']+)' /.exec(a.message||a.description);n&&(e.column=k(n[1],f.url,f.line))}i[""+l]?j=!0:i[""+l]=!0,h.push(e)}b&&h.splice(0,b);var o={name:a.name,message:a.message,url:document.location.href,stack:h};return q(o,a.sourceURL||a.fileName,a.line||a.lineNumber,a.message||a.description),o}function s(a,b){var c=null;b=null==b?0:+b;try{if(c=o(a))return c}catch(d){if(t)throw d}try{if(c=n(a))return c}catch(d){if(t)throw d}try{if(c=p(a))return c}catch(d){if(t)throw d}try{if(c=r(a,b+1))return c}catch(d){if(t)throw d}return{}}var t=!1,u={};return s.augmentStackTraceWithInitialElement=q,s.computeStackTraceFromStackProp=n,s.guessFunctionName=d,s.gatherContext=e,s}();var H,I,J,K,L,M,N,O=a.Raven,P=!("object"!=typeof JSON||!JSON.stringify),Q={logger:"javascript",ignoreErrors:[],ignoreUrls:[],whitelistUrls:[],includePaths:[],collectWindowErrors:!0,tags:{},maxMessageLength:100,extra:{}},R=!1,S=Object.prototype,T=u(),U={VERSION:"1.1.19",debug:!0,noConflict:function(){return a.Raven=O,U},config:function(a,b){if(J)return C("error","Error: Raven has already been configured"),U;if(!a)return U;var c=e(a),d=c.path.lastIndexOf("/"),f=c.path.substr(1,d);return b&&m(b,function(a,b){Q[a]=b}),Q.ignoreErrors.push(/^Script error\.?$/),Q.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/),Q.ignoreErrors=A(Q.ignoreErrors),Q.ignoreUrls=Q.ignoreUrls.length?A(Q.ignoreUrls):!1,Q.whitelistUrls=Q.whitelistUrls.length?A(Q.whitelistUrls):!1,Q.includePaths=A(Q.includePaths),L=c.user,M=c.path.substr(d+1),J="//"+c.host+(c.port?":"+c.port:"")+"/"+f+"api/"+M+"/store/",c.protocol&&(J=c.protocol+":"+J),Q.fetchContext&&(E.remoteFetching=!0),Q.linesOfContext&&(E.linesOfContext=Q.linesOfContext),E.collectWindowErrors=!!Q.collectWindowErrors,n(),U},install:function(){return z()&&!R&&(E.report.subscribe(o),R=!0),U},context:function(a,c,d){return g(a)&&(d=c||[],c=a,a=b),U.wrap(a,c).apply(this,d)},wrap:function(a,c){function d(){for(var b=[],d=arguments.length,e=!a||a&&a.deep!==!1;d--;)b[d]=e?U.wrap(a,arguments[d]):arguments[d];try{return c.apply(this,b)}catch(f){throw U.captureException(f,a),f}}if(f(c)&&!g(a))return a;if(g(a)&&(c=a,a=b),!g(c))return c;if(c.__raven__)return c;for(var e in c)l(c,e)&&(d[e]=c[e]);return d.__raven__=!0,d.__inner__=c,d},uninstall:function(){return E.report.uninstall(),R=!1,U},captureException:function(a,b){if(!k(a))return U.captureMessage(a,b);H=a;try{E.report(a,b)}catch(c){if(a!==c)throw c}return U},captureMessage:function(a,b){return Q.ignoreErrors.test&&Q.ignoreErrors.test(a)?void 0:(w(s({message:a+""},b)),U)},setUserContext:function(a){return K=a,U},setExtraContext:function(a){return Q.extra=a||{},U},setTagsContext:function(a){return Q.tags=a||{},U},setReleaseContext:function(a){return Q.release=a,U},setDataCallback:function(a){return Q.dataCallback=a,U},setShouldSendCallback:function(a){return Q.shouldSendCallback=a,U},lastException:function(){return H},lastEventId:function(){return I},isSetup:function(){return z()}};U.setUser=U.setUserContext;var V="source protocol user pass host port path".split(" "),W=/^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/;d.prototype=new Error,d.prototype.constructor=d,D(),"function"==typeof define&&define.amd?(a.Raven=U,define("raven",[],function(){return U})):"object"==typeof module?module.exports=U:"object"==typeof exports?exports=U:a.Raven=U}("undefined"!=typeof window?window:this); +//# sourceMappingURL=raven.min.map \ No newline at end of file diff --git a/htdocs/includes/raven-js/dist/raven.min.map b/htdocs/includes/raven-js/dist/raven.min.map new file mode 100644 index 0000000000000000000000000000000000000000..95d9b53536b18644d4669ee34e9dc2532c786844 --- /dev/null +++ b/htdocs/includes/raven-js/dist/raven.min.map @@ -0,0 +1 @@ +{"version":3,"file":"raven.min.js","sources":["raven.js"],"names":["window","undefined","triggerEvent","eventType","options","event","key","substr","toUpperCase","document","createEvent","initEvent","createEventObject","hasKey","dispatchEvent","fireEvent","toLowerCase","e","RavenConfigError","message","this","name","parseDSN","str","m","dsnPattern","exec","dsn","i","dsnKeys","pass","isUndefined","what","isFunction","isString","objectPrototype","toString","call","isObject","isEmptyObject","k","isError","Error","object","hasOwnProperty","each","obj","callback","j","length","setAuthQueryString","authQueryString","Raven","VERSION","globalKey","handleStackInfo","stackInfo","frames","stack","frame","normalizeFrame","push","processException","url","lineno","normalized","filename","line","colno","column","function","func","context","extractContextFromFrame","keys","in_app","globalOptions","includePaths","test","fetchContext","pivot","isMinified","slice","type","fileurl","stacktrace","label","ignoreErrors","reverse","truncate","maxMessageLength","ignoreUrls","whitelistUrls","send","objectMerge","exception","value","culprit","obj1","obj2","max","now","Date","getHttpData","http","location","href","headers","User-Agent","navigator","userAgent","referrer","Referer","data","isSetup","project","globalProject","logger","platform","request","tags","extra","session:duration","startTime","globalUser","user","release","dataCallback","shouldSendCallback","lastEventId","event_id","uuid4","makeRequest","img","newImage","src","globalServer","encodeURIComponent","JSON","stringify","crossOrigin","onload","onerror","onabort","createElement","hasJSON","logDebug","joinRegExp","patterns","pattern","sources","len","replace","source","RegExp","join","c","r","Math","random","v","level","console","debug","afterLoad","RavenConfig","config","install","TraceKit","remoteFetching","collectWindowErrors","linesOfContext","_slice","UNKNOWN_FUNCTION","wrap","wrapped","apply","arguments","report","subscribe","handler","installGlobalHandler","handlers","unsubscribe","splice","unsubscribeAll","uninstallGlobalHandler","notifyHandlers","isWindowError","concat","inner","traceKitWindowOnError","lineNo","colNo","ex","lastExceptionStack","computeStackTrace","augmentStackTraceWithInitialElement","processLastException","guessFunctionName","gatherContext","_oldOnerrorHandler","_onErrorHandlerInstalled","_lastExceptionStack","_lastArgs","lastArgs","lastException","rethrow","args","setTimeout","incomplete","uninstall","loadSource","getXHR","XMLHttpRequest","ActiveXObject","open","responseText","getSource","sourceCache","indexOf","domain","split","reFunctionArgNames","reGuessFunction","maxLines","linesBefore","floor","linesAfter","start","end","min","escapeRegExp","text","escapeCodeAsRegExpForMatchingInsideHTML","body","findSourceInUrls","re","urls","substring","index","lastIndexOf","findSourceInLine","fragment","findSourceByFunctionBody","parts","result","scripts","getElementsByTagName","code","codeRE","eventRE","script","computeStackTraceFromStackProp","element","chrome","gecko","lines","reference","columnNumber","computeStackTraceFromStacktraceProp","testRE","exc","computeStackTraceFromOperaMultiLineMessage","lineRE1","lineRE2","lineRE3","inlineScriptBlocks","item","relativeLine","pos","innerText","midline","initial","unshift","partial","computeStackTraceByWalkingCallerChain","depth","functionName","funcs","recursion","curr","caller","description","sourceURL","fileName","lineNumber","lastCapturedException","_Raven","isRavenInstalled","Object","prototype","noConflict","uri","lastSlash","path","host","port","protocol","deep","captureException","__raven__","property","__inner__","captureMessage","ex1","msg","setUserContext","setExtraContext","setTagsContext","setReleaseContext","setDataCallback","setShouldSendCallback","setUser","constructor","define","amd","module","exports"],"mappings":"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"} \ No newline at end of file diff --git a/htdocs/includes/raven-js/docs/Makefile b/htdocs/includes/raven-js/docs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..991be4fb7fb9e4eb7739e6748e8f44046f64c96f --- /dev/null +++ b/htdocs/includes/raven-js/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = . + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-js.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-js.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-js" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-js" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/htdocs/includes/raven-js/docs/changelog/index.rst b/htdocs/includes/raven-js/docs/changelog/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..f1f160cc7e608b67f917c8945dc90909aa98913a --- /dev/null +++ b/htdocs/includes/raven-js/docs/changelog/index.rst @@ -0,0 +1,138 @@ +Changelog +========= + +1.1.19 +~~~~~~ +* Use more compliant way of creating an Image in the dom. See: https://github.com/getsentry/raven-js/pull/334 +* `String` objects weren't getting identified as a string. See: https://github.com/getsentry/raven-js/pull/336 +* Expose getter/setter for dataCallback and shouldSendCallback +* Better handle if/when the dataCallback returns garbage +* Fix support for nodeunit. See: https://github.com/getsentry/raven-js/pull/338 +* Fix `console.warn` sending as a `warning` level to server. See: https://github.com/getsentry/raven-js/issues/342 +* Improve the capture of unhandled errors from promises in Ember plugin. See: https://github.com/getsentry/raven-js/pull/330 + +1.1.18 +~~~~~~ +* Fixed a trailing comma which would make IE8 cry. This affects the uncompressed builds only. Compressed builds were unaffected. See: https://github.com/getsentry/raven-js/pull/333 + +1.1.17 +~~~~~~ +* Better support for Angular errors. See: https://github.com/getsentry/raven-js/pull/238 +* Allow setting truncate length through ``globalOptions.maxMessageLength``. See: https://github.com/getsentry/raven-js/pull/246 +* Fixed the pattern for parsing gecko stacktraces. See: https://github.com/getsentry/raven-js/pull/252 +* Browserify support. See: https://github.com/getsentry/raven-js/pull/253, https://github.com/getsentry/raven-js/pull/260, https://github.com/getsentry/raven-js/pull/261 +* Start tracking ``session:duration`` automatically as metadata. +* Fix globalOptions overwrite. See: https://github.com/getsentry/raven-js/pull/264 +* Better cross origin support. See: https://github.com/getsentry/raven-js/pull/276 +* Better anonymous function support in Chrome stack trace parsing. See: https://github.com/getsentry/raven-js/pull/290, https://github.com/getsentry/raven-js/pull/294 +* Remove deprecated ``site`` param. +* New ``Raven.isSetup()``. See: https://github.com/getsentry/raven-js/pull/309 +* Better backbone.js support. See: https://github.com/getsentry/raven-js/pull/307 +* ``ignoreErrors`` now also is applied to ``captureMessage()``. See: https://github.com/getsentry/raven-js/pull/312 +* Capture unhandled errors from promises in Ember. See: https://github.com/getsentry/raven-js/pull/319 +* Add new support for ``releases``. See: https://github.com/getsentry/raven-js/issues/325 + +1.1.16 +~~~~~~ +* Fixed a bug that was preventing stack frames from ``raven.js`` from being hidden correctly. See: https://github.com/getsentry/raven-js/pull/216 +* Fixed an IE bug with the ``console`` plugin. See: https://github.com/getsentry/raven-js/issues/217 +* Added support for ``chrome-extension://`` protocol in Chrome in stack traces. +* Added ``setExtraContext`` and ``setTagsContext``. See: https://github.com/getsentry/raven-js/pull/219 +* Renamed ``setUser`` to ``setUserContext`` to match. ``setUser`` still exists, but will be deprecated in a future release. +* New ``backbone.js`` plugin. See: https://github.com/getsentry/raven-js/pull/220 +* Added support for ``chrome://`` protocol in Firefox in stack traces. See: https://github.com/getsentry/raven-js/pull/225 +* Ignore more garbage from IE cross origin errors. See: https://github.com/getsentry/raven-js/pull/224 +* Added ``Raven.debug`` to prevent logging to ``console`` when ``false``. Defaults to ``true`` for backwards compatability. See: https://github.com/getsentry/raven-js/pull/229 +* Prevent calling ``Raven.config()`` or ``Raven.install()`` twice. See: https://github.com/getsentry/raven-js/pull/233 + +1.1.15 +~~~~~~ +* Fix issues if a non-string were passed to ``Raven.captureMessage`` and non-Error objects were passed to ``Raven.captureException``. + +1.1.14 +~~~~~~ +* Only filter normal Error objects without a message, not all of them. Turns out, people throw errors like this. Ahem, Underscore.js. See: https://github.com/jashkenas/underscore/pull/1589/files + +1.1.13 +~~~~~~ +* Fixed a unicode issue in the previous release. + +1.1.12 +~~~~~~ +* Fix a bug using the ``console`` plugin with older IE. See: https://github.com/getsentry/raven-js/pull/192 +* Added initial ``ember.js`` plugin for early testing and feedback. +* Added initial ``angular.js`` plugin for early testing and feedback. +* Fixed an issue with the ``require.js`` plugin basically not working at all. See: https://github.com/getsentry/raven-js/commit/c2a2e2672a2a61a5a07e88f24a9c885f6dba57ae +* Got rid of ``Raven.afterLoad`` and made it internal only. +* ``Raven.TraceKit`` is now internal only. +* Truncate message length to a max of 100 characters becasue angular.js sucks and generates stupidly large error messages. + +1.1.11 +~~~~~~ +* Capture column number from FireFox +* Fix propagation of extra options through ``captureException``, see: https://github.com/getsentry/raven-js/pull/189 +* Fix a minor bug that causes TraceKit to blow up of someone passes something dumb through ``window.onerror`` + +1.1.10 +~~~~~~ +* A falsey DSN value disables Raven without yelling about an invalid DSN. + +1.1.9 +~~~~~ +* Added ``Raven.lastEventId()`` to get back the Sentry event id. See: http://raven-js.readthedocs.org/en/latest/usage/index.html#getting-back-an-event-id +* Fixed a bug in the ``console`` plugin. See: https://github.com/getsentry/raven-js/pull/181 +* Provide a way out of deep wrapping arguments. See: https://github.com/getsentry/raven-js/pull/182 +* ``Raven.uninstall()`` actually removes the patched ``window.onerror``. +* No more globally exposed ``TraceKit``! + +1.1.8 +~~~~~ +* Fixed a bug in IE8. See: https://github.com/getsentry/raven-js/pull/179 + +1.1.4-1.1.7 +~~~~~~~~~~~ +These were a bunch of super small incremental updates trying to get better integration and better support inside Sentry itself. + +* Culprit determined from the src url of the offending script, not the url of the page. +* Send Sentry the frames in the right order. They were being sent in reverse. Somehow nobody noticed this. +* Support for Chrome's new window.onerror api. See: https://github.com/getsentry/raven-js/issues/172 + +1.1.3 +~~~~~ +* When loading with an AMD loader present, do not automatically call ``Raven.noConflict()``. This was causing issues with using plugins. See: https://github.com/getsentry/raven-js/pull/165 +* https://github.com/getsentry/raven-js/pull/168 + +1.1.2 +~~~~~ +* An invalid DSN will now raise a RavenConfigError instead of some cryptic error +* Will raise a RavenConfigError when supplying the private key part of the DSN since this isn't applicable for raven.js and is harmful to include +* https://github.com/getsentry/raven-js/issues/128 + +1.1.1 +~~~~~ +* Fixed a bug in parsing some DSNs. See: https://github.com/getsentry/raven-js/issues/160 + +1.1.0 +~~~~~ + +Plugins +------- +If you're upgrading from 1.0.x, 2 "plugins" were included with the package. These 2 plugins are now stripped out of core and included as the ``jquery`` and ``native`` plugins. If you'd like to start using 1.1.0 and maintain existing functionality, you'll want to use: http://cdn.ravenjs.com/1.1.0/jquery,native/raven.min.js For a list of other plugins, checkout http://ravenjs.com + +ravenjs.com +----------- +A new website dedicated to helping you compile a custom build of raven.js + +whitelistUrls +------------- +``whitelistUrls`` are recommended over ``ignoreUrls``. ``whitelistUrls`` drastically helps cut out noisy error messages from other scripts running on your site. + +Misc +---- +* ``ignoreUrls``, ``ignoreErrors``, ``includePaths`` have all been unified to accept both a regular expression and strings to avoid confusion and backwards compatability +* ``Raven.wrap`` recursively wraps arguments +* Events are dispatched when an exception is received, recorded or failed sending to Sentry +* Support newer Sentry protocol which allows smaller packets +* Allow loading raven async with RavenConfig +* Entirely new build system with Grunt +* ``options.collectWindowErrors`` to tell Raven to ignore window.onerror diff --git a/htdocs/includes/raven-js/docs/conf.py b/htdocs/includes/raven-js/docs/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..8e86ca966c492245c2faf0fc032bc1cd4620345c --- /dev/null +++ b/htdocs/includes/raven-js/docs/conf.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# +# raven-js documentation build configuration file, created by +# sphinx-quickstart on Mon Jan 21 21:04:27 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, datetime + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'raven-js' +copyright = u'%s, Matt Robenolt' % datetime.date.today().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +import json +# The full version, including alpha/beta/rc tags. +release = json.load(open('../package.json'))['version'] +# The short X.Y version. +version = release.rsplit('.', 1)[0] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'raven-jsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'raven-js.tex', u'raven-js Documentation', + u'Matt Robenolt', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'raven-js', u'raven-js Documentation', + [u'Matt Robenolt'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'raven-js', u'raven-js Documentation', + u'Matt Robenolt', 'raven-js', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/htdocs/includes/raven-js/docs/config/index.rst b/htdocs/includes/raven-js/docs/config/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..571e266d95ef920fdb6a74271955017a248fb707 --- /dev/null +++ b/htdocs/includes/raven-js/docs/config/index.rst @@ -0,0 +1,220 @@ +Configuration +============= + +We must first configure Sentry to allow certain hosts to report errors. This prevents abuse so somebody else couldn't start sending errors to your account from their site. + +**Note**: Without setting this, all messages will be rejected! + +This can be found under the *Project Details* page in Sentry. + +.. image:: https://i.imgur.com/S09MeSM.png + +Now need to set up Raven.js to use your Sentry DSN. + +.. code-block:: javascript + + Raven.config('https://public@getsentry.com/1').install() + +At this point, Raven is ready to capture any uncaught exception. + +Although, this technically works, this is not going to yield the greatest results. It's highly recommended to next check out :doc:`/usage/index`. + +Optional settings +~~~~~~~~~~~~~~~~~ + +``Raven.config()`` can be passed an optional object for extra configuration. + +logger +------ + +The name of the logger used by Sentry. Default: ``javascript`` + +.. code-block:: javascript + + { + logger: 'javascript' + } + +release +------- + +Track the version of your application in Sentry. + +.. code-block:: javascript + + { + release: '721e41770371db95eee98ca2707686226b993eda' + } + +Can also be defined with ``Raven.setReleaseContext('721e41770371db95eee98ca2707686226b993eda')``. + +.. _config-whitelist-urls: + +tags +---- + +Additional `tags <https://www.getsentry.com/docs/tags/>`__ to assign to each event. + +.. code-block:: javascript + + { + tags: {git_commit: 'c0deb10c4'} + } + +whitelistUrls +------------- + +The inverse of ``ignoreUrls``. Only report errors from whole urls matching a regex pattern or an exact string. ``whitelistUrls`` should match the url of your actual JavaScript files. It should match the url of your site if and only if you are inlining code inside ``<script>`` tags. + +Does not affect captureMessage or when non-error object is passed in as argument to captureException. + +.. code-block:: javascript + + { + whitelistUrls: [/getsentry\.com/, /cdn\.getsentry\.com/] + } + +ignoreErrors +------------ + +Very often, you will come across specific errors that are a result of something other than your application, or errors that you're completely not interested in. `ignoreErrors` is a list of these messages to be filtered out before being sent to Sentry as either regular expressions or strings. + +Does not affect captureMessage or when non-error object is passed in as argument to captureException. + +.. code-block:: javascript + + { + ignoreErrors: ['fb_xd_fragment'] + } + +ignoreUrls +---------- + +The inverse of ``whitelistUrls`` and similar to ``ignoreErrors``, but will ignore errors from whole urls matching a regex pattern or an exact string. + +.. code-block:: javascript + + { + ignoreUrls: [/graph\.facebook\.com/, 'http://example.com/script2.js'] + } + +Does not affect captureMessage or when non-error object is passed in as argument to captureException. + +includePaths +------------ + +An array of regex patterns to indicate which urls are a part of your app in the stack trace. All other frames will appear collapsed inside Sentry to make it easier to discern between frames that happened in your code vs other code. It'd be suggested to add the current page url, and the host for your CDN. + +.. code-block:: javascript + + { + includePaths: [/https?:\/\/getsentry\.com/, /https?:\/\/cdn\.getsentry\.com/] + } + +dataCallback +------------ + +A function that allows mutation of the data payload right before being sent to Sentry. + +.. code-block:: javascript + + { + dataCallback: function(data) { + // do something to data + return data; + } + } + +Can also be set at runtime with `Raven.setDataCallback(function(data) { ... })`. + +shouldSendCallback +------------------ + +A callback function that allows you to apply your own filters to determine if the message should be sent to Sentry. + +.. code-block:: javascript + + { + shouldSendCallback: function(data) { + return false; + } + } + +Can also be set at runtime with `Raven.setShouldSendCallback(function(data) { ... })`. + +maxMessageLength +------------------ + +By default, raven truncates messages to a max length of 100 characters. You can customize the max length with this parameter. + + +Putting it all together +~~~~~~~~~~~~~~~~~~~~~~~ + +.. parsed-literal:: + + <!DOCTYPE html> + <html> + <head> + <title>Awesome stuff happening here</title> + </head> + <body> + ... + <script src="jquery.min.js"></script> + <script src="//cdn.ravenjs.com/|release|/jquery,native/raven.min.js"></script> + <script> + var options = { + logger: 'my-logger', + whitelistUrls: [ + /disqus\\.com/, /getsentry\\.com/ + ], + ignoreErrors: [ + 'fb_xd_fragment', /ReferenceError:.*/ + ], + includePaths: [ + /https?:\\/\\/(www\\.)?getsentry\\.com/ + ] + }; + Raven.config('https://public@app.getsentry.com/1', options).install(); + </script> + <script src="myapp.js"></script> + </body> + </html> + +TraceKit specific optional settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Usually there is no need to touch these settings, but they exist in case you need to tweak something. + +fetchContext +------------ + +Enable TraceKit to attempt to fetch source files to look up anonymous function names, this can be useful to enable if you don't get the context for some entries in the stack trace. Default value is ``false``. + +.. code-block:: javascript + + { + fetchContext: true + } + +linesOfContext +-------------- + +The count of lines surrounding the error line that should be used as context in the stack trace, default value is ``11``. Only applicable when ``fetchContext` is enabled. + +.. code-block:: javascript + + { + linesOfContext: 11 + } + +collectWindowErrors +------------------- + +Enable or disable the TraceKit ``window.onerror`` handler, default value is ``true``. + +.. code-block:: javascript + + { + collectWindowErrors: true + } diff --git a/htdocs/includes/raven-js/docs/contributing/index.rst b/htdocs/includes/raven-js/docs/contributing/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..2b25abf7f16dac8c56315051fc9237180892f2af --- /dev/null +++ b/htdocs/includes/raven-js/docs/contributing/index.rst @@ -0,0 +1,99 @@ +Contributing +============ + +Setting up an Environment +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run the test suite and run our code linter, node.js and npm are required. If you don't have node installed, `get it here <http://nodejs.org/download/>`_ first. + +Installing all other dependencies is as simple as: + +.. code-block:: sh + + $ npm install + +And if you don't have `Grunt <http://gruntjs.com/>`_ already, feel free to install that globally: + +.. code-block:: sh + + $ npm install -g grunt-cli + +Running the Test Suite +~~~~~~~~~~~~~~~~~~~~~~ + +The test suite is powered by `Mocha <http://visionmedia.github.com/mocha/>`_ and can both run from the command line, or in the browser. + +From the command line: + +.. code-block:: sh + + $ grunt test + +From your browser: + +.. code-block:: sh + + $ grunt run:test + +Then visit: http://localhost:8000/test/ + +Compiling Raven.js +~~~~~~~~~~~~~~~~~~ + +The simplest way to compile your own version of Raven.js is with the supplied grunt command: + +.. code-block:: sh + + $ grunt build + +By default, this will compile raven.js and all of the included plugins. + +If you only want to compile the core raven.js: + +.. code-block:: sh + + $ grunt build.core + +Files are compiled into ``build/``. + +Contributing Back Code +~~~~~~~~~~~~~~~~~~~~~~ + +Please, send over suggestions and bug fixes in the form of pull requests on `GitHub <https://github.com/getsentry/raven-js>`_. Any nontrivial fixes/features should include tests. +Do not include any changes to the ``dist/`` folder or bump version numbers yourself. + +Documentation +------------- + +The documentation is written using `reStructuredText <http://en.wikipedia.org/wiki/ReStructuredText>`_, and compiled using `Sphinx <http://sphinx-doc.org/>`_. If you don't have Sphinx installed, you can do it using following command (assuming you have Python already installed in your system): + +.. code-block:: sh + + $ pip install sphinx + +Documentation can be then compiled by running: + +.. code-block:: sh + + $ make docs + +Afterwards you can view it in your browser by running following command and than pointing your browser to http://127.0.0.1:8000/: + +.. code-block:: sh + + $ grunt run:docs + + + +Releasing New Version +~~~~~~~~~~~~~~~~~~~~~ + +* Bump version numbers in both ``package.json`` and ``bower.json``. +* ``$ grunt dist`` This will compile a new version and update it in the ``dist/`` folder. +* Confirm that build was fine, etc. +* Commit new version, create a tag. Push to GitHub. +* ``$ grunt publish`` to recompile all plugins and all permutations and upload to S3. +* ``$ npm publish`` to push to npm. +* Confirm that the new version exists behind ``cdn.ravenjs.com`` +* Update version in the ``gh-pages`` branch specifically for http://ravenjs.com/. +* glhf diff --git a/htdocs/includes/raven-js/docs/index.rst b/htdocs/includes/raven-js/docs/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..afe07bc9ab949e2c90559749e148a593ac3db675 --- /dev/null +++ b/htdocs/includes/raven-js/docs/index.rst @@ -0,0 +1,42 @@ +Raven.js +======== + +Raven.js is a tiny standalone JavaScript client for `Sentry <http://www.getsentry.com/>`_. + +**This version of Raven.js requires Sentry 6.0 or newer.** + + +Getting Started +--------------- + +.. toctree:: + :maxdepth: 2 + + install/index + plugins/index + config/index + usage/index + tips/index + +Developers +---------- +.. toctree:: + :maxdepth: 2 + + contributing/index + +What's New? +----------- +.. toctree:: + :maxdepth: 2 + + changelog/index + +Resources +--------- +* `Download <http://ravenjs.com>`_ +* `Bug Tracker <https://github.com/getsentry/raven-js/issues>`_ +* `Code <https://github.com/getsentry/raven-js>`_ +* `IRC <irc://irc.freenode.net/sentry>`_ (irc.freenode.net, #sentry) +* :doc:`Changelog </changelog/index>` +* Follow `@mattrobenolt <https://twitter.com/mattrobenolt>`_ on Twitter for updates! diff --git a/htdocs/includes/raven-js/docs/install/index.rst b/htdocs/includes/raven-js/docs/install/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..17feda4e08efd15cb412ce9bb563649f1d1c6187 --- /dev/null +++ b/htdocs/includes/raven-js/docs/install/index.rst @@ -0,0 +1,61 @@ +Installation +============ + +Raven is distributed in a few different methods, and should get included after any other libraries are included, but before your own scripts. + +So for example: + +.. parsed-literal:: + + <script src="jquery.js"></script> + <script src="//cdn.ravenjs.com/|release|/jquery,native/raven.min.js"></script> + <script>Raven.config('...').install();</script> + <script src="app.js"></script> + +This allows the ability for Raven's plugins to instrument themselves. If included before something like jQuery, it'd be impossible to use for example, the jquery plugin. + +Using our CDN +~~~~~~~~~~~~~ + +We serve our own builds off of `Fastly <http://www.fastly.com/>`_. They are accessible over both http and https, so we recommend leaving the protocol off. + +Our CDN distributes builds with and without :doc:`plugins </plugins/index>`. + +.. parsed-literal:: + + <script src="//cdn.ravenjs.com/|release|/raven.min.js"></script> + +**We highly recommend trying out a plugin or two since it'll greatly improve the chances that we can collect good information.** + +This version does not include any plugins. See `ravenjs.com <http://ravenjs.com/>`_ for more information about plugins and getting other builds. + +Bower +~~~~~ + +We also provide a way to deploy Raven via `bower +<http://bower.io/>`_. Useful if you want serve your own scripts instead of depending on our CDN and mantain a ``bower.json`` with a list of dependencies and versions (adding the ``--save`` flag would automatically add it to ``bower.json``). + +.. code-block:: sh + + $ bower install raven-js --save + +.. code-block:: html + + <script src="/bower_components/raven-js/dist/raven.js"></script> + +Also note that the file is uncompresed but is ready to pass to any decent JavaScript compressor like `uglify <https://github.com/mishoo/UglifyJS2>`_. + +npm +~~~ + +Raven is published to npm as well. https://www.npmjs.com/package/raven-js + +.. code-block:: sh + + $ npm install raven-js --save + +Requirements +~~~~~~~~~~~~ + +Raven expects the browser to provide `window.JSON` and `window.JSON.stringify`. In Internet Explorer 8+ these are available in `standards mode <http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx>`_. +You can also use `json2.js <https://github.com/douglascrockford/JSON-js>`_ to provide the JSON implementation in browsers/modes which doesn't support native JSON diff --git a/htdocs/includes/raven-js/docs/make.bat b/htdocs/includes/raven-js/docs/make.bat new file mode 100644 index 0000000000000000000000000000000000000000..13e2848a47d06ef1697fbed01bc0cb5ef7dfec5d --- /dev/null +++ b/htdocs/includes/raven-js/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-js.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-js.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/htdocs/includes/raven-js/docs/plugins/index.rst b/htdocs/includes/raven-js/docs/plugins/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..7a9df7b019ea5d16a8a79321f5cfcf80acfdebcb --- /dev/null +++ b/htdocs/includes/raven-js/docs/plugins/index.rst @@ -0,0 +1,20 @@ +Plugins +======= + +What are plugins? +~~~~~~~~~~~~~~~~~ + +In Raven.js, plugins are little snippets of code to augment functionality for a specific application/framework. It is highly recommended to checkout the list of plugins and use what apply to your project. + +In order to keep the core small, we have opted to only include the most basic functionality by default, and you can pick and choose which plugins are applicable for you. + +Why are plugins needed at all? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +JavaScript is pretty restrictive when it comes to exception handling, and there are a lot of things that make it difficult to get relevent information, so it's important that we inject code and wrap things magically so we can extract what we need. See :doc:`/usage/index` for tips regarding that. + + +All Plugins +~~~~~~~~~~~ +* https://github.com/getsentry/raven-js/tree/master/plugins +* `Download <http://ravenjs.com>`_ diff --git a/htdocs/includes/raven-js/docs/tips/index.rst b/htdocs/includes/raven-js/docs/tips/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..8dee09eb7ef243ee25e54d62c3663be59d4b4333 --- /dev/null +++ b/htdocs/includes/raven-js/docs/tips/index.rst @@ -0,0 +1,79 @@ +Pro Tips™ +========= + + +Decluttering Sentry +~~~~~~~~~~~~~~~~~~~ + +The first thing to do is to consider constructing a whitelist of domains in which might raise acceptable exceptions. + +If your scripts are loaded from ``cdn.example.com`` and your site is ``example.com`` it'd be reasonable to set ``whitelistUrls`` to: + +.. code-block:: javascript + + whitelistUrls: [ + /https?:\/\/((cdn|www)\.)?example\.com/ + ] + +Since this accepts a regular expression, that would catch anything \*.example.com or example.com exactly. See also: :ref:`Config: whitelistUrls<config-whitelist-urls>`. + +Next, checkout the list of :doc:`plugins </plugins/index>` we provide and see which are applicable to you. + +The community has compiled a list of common ignore rules for common things, like Facebook, Chrome extensions, etc. So it's recommended to at least check these out and see if they apply to you. `Check out the original gist <https://gist.github.com/impressiver/5092952>`_. + +.. code-block:: javascript + + var ravenOptions = { + ignoreErrors: [ + // Random plugins/extensions + 'top.GLOBALS', + // See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html + 'originalCreateNotification', + 'canvas.contentDocument', + 'MyApp_RemoveAllHighlights', + 'http://tt.epicplay.com', + 'Can\'t find variable: ZiteReader', + 'jigsaw is not defined', + 'ComboSearch is not defined', + 'http://loading.retry.widdit.com/', + 'atomicFindClose', + // Facebook borked + 'fb_xd_fragment', + // ISP "optimizing" proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha) + // See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy + 'bmi_SafeAddOnload', + 'EBCallBackMessageReceived', + // See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx + 'conduitPage' + ], + ignoreUrls: [ + // Facebook flakiness + /graph\.facebook\.com/i, + // Facebook blocked + /connect\.facebook\.net\/en_US\/all\.js/i, + // Woopra flakiness + /eatdifferent\.com\.woopra-ns\.com/i, + /static\.woopra\.com\/js\/woopra\.js/i, + // Chrome extensions + /extensions\//i, + /^chrome:\/\//i, + // Other plugins + /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb + /webappstoolbarba\.texthelp\.com\//i, + /metrics\.itunes\.apple\.com\.edgesuite\.net\//i + ] + }; + + +Sampling Data +~~~~~~~~~~~~~ + +It happens frequently that errors sent from your frontend can be overwhelming. One solution here is to only send a sample of the events that happen. You can do this via the ``shouldSendCallback`` setting: + +.. code-block:: javascript + + shouldSendCallback: function(data) { + // only send 10% of errors + var sampleRate = 10; + return (Math.random() * 100 <= sampleRate); + } diff --git a/htdocs/includes/raven-js/docs/usage/index.rst b/htdocs/includes/raven-js/docs/usage/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..c8e01f9c80170624e6413ada47cbe857a9219156 --- /dev/null +++ b/htdocs/includes/raven-js/docs/usage/index.rst @@ -0,0 +1,156 @@ +Usage +===== + +By default, Raven makes a few efforts to try its best to capture meaningful stack traces, but browsers make it pretty difficult. + +The easiest solution is to prevent an error from bubbling all of the way up the stack to ``window``. + +How to actually capture an error correctly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +try...catch +----------- + +The simplest way, is to try and explicitly capture and report potentially problematic code with a ``try...catch`` block and ``Raven.captureException``. + +.. code-block:: javascript + + try { + doSomething(a[0]) + } catch(e) { + Raven.captureException(e) + } + +**Do not** throw strings! Always throw an actual ``Error`` object. For example: + +.. code-block:: javascript + + throw new Error('broken') // good + throw 'broken' // bad + +It's impossible to retrieve a stack trace from a string. If this happens, Raven transmits the error as a plain message. + +context/wrap +------------ + +``Raven.context`` allows you to wrap any function to be immediately executed. Behind the scenes, Raven is just wrapping your code in a ``try...catch`` block to record the exception before re-throwing it. + +.. code-block:: javascript + + Raven.context(function() { + doSomething(a[0]) + }) + +``Raven.wrap`` wraps a function in a similar way to ``Raven.context``, but instead of executing the function, it returns another function. This is totally awesome for use when passing around a callback. + +.. code-block:: javascript + + var doIt = function() { + // doing cool stuff + } + + setTimeout(Raven.wrap(doIt), 1000) + +Tracking authenticated users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While a user is logged in, you can tell Sentry to associate errors with user data. + +.. code-block:: javascript + + Raven.setUserContext({ + email: 'matt@example.com', + id: '123' + }) + +If at any point, the user becomes unauthenticated, you can call ``Raven.setUserContext()`` with no arguments to remove their data. *This would only really be useful in a large web app where the user logs in/out without a page reload.* + +Capturing a specific message +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + Raven.captureMessage('Broken!') + +Passing additional data +~~~~~~~~~~~~~~~~~~~~~~~ + +``captureException``, ``context``, ``wrap``, and ``captureMessage`` functions all allow passing additional data to be tagged onto the error, such as ``tags`` or ``extra`` for additional context. + +.. code-block:: javascript + + Raven.captureException(e, {tags: { key: "value" }}) + + Raven.captureMessage('Broken!', {tags: { key: "value" }}) + + Raven.context({tags: { key: "value" }}, function(){ ... }) + + Raven.wrap({logger: "my.module"}, function(){ ... }) + + Raven.captureException(e, {extra: { foo: "bar" }}) + +You can also set context variables globally to be merged in with future exceptions with ``setExtraContext`` and ``setTagsContext``. + +.. code-block:: javascript + + Raven.setExtraContext({ foo: "bar" }) + Raven.setTagsContext({ key: "value" }) + + +Getting back an event id +~~~~~~~~~~~~~~~~~~~~~~~~ + +An event id is a globally unique id for the event that was just sent. This event id can be used to find the exact event from within Sentry. + +This is often used to display for the user and report an error to customer service. + +.. code-block:: javascript + + Raven.lastEventId() + +``Raven.lastEventId()`` will be undefined until an event is sent. After an event is sent, it will contain the string id. + +.. code-block:: javascript + + Raven.captureMessage('Broken!') + alert(Raven.lastEventId()) + + +Check if Raven is setup and ready to go +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + Raven.isSetup() + + +Dealing with minified source code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Raven and Sentry now support `Source Maps <http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/>`_. + +We have provided some instructions to creating Source Maps over at https://www.getsentry.com/docs/sourcemaps/. Also, checkout our `Gruntfile <https://github.com/getsentry/raven-js/blob/master/Gruntfile.js>`_ for a good example of what we're doing. + +You can use `Source Map Validator <http://sourcemap-validator.herokuapp.com/>`_ to help verify that things are correct. + +CORS +~~~~ + +If you're hosting your scripts on another domain and things don't get caught by Raven, it's likely that the error will bubble up to ``window.onerror``. If this happens, the error will report some ugly ``Script error`` and Raven will drop it on the floor +since this is a useless error for everybody. + +To help mitigate this, we can tell the browser that these scripts are safe and we're allowing them to expose their errors to us. + +In your ``<script>`` tag, specify the ``crossorigin`` attribute: + +.. code-block:: html + + <script src="//cdn.example.com/script.js" crossorigin="anonymous"></script> + +And set an ``Access-Control-Allow-Origin`` HTTP header on that file. + +.. code-block:: console + + Access-Control-Allow-Origin: * + +**Note: both of these steps need to be done or your scripts might not even get executed** diff --git a/htdocs/includes/raven-js/example/Makefile b/htdocs/includes/raven-js/example/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..87976776e90f98235e4870a8a88272639aa5ddcc --- /dev/null +++ b/htdocs/includes/raven-js/example/Makefile @@ -0,0 +1,3 @@ + +build: + ../node_modules/.bin/uglifyjs --source-map=file.sourcemap.js -c -o file.min.js file1.js file2.js diff --git a/htdocs/includes/raven-js/example/file.min.js b/htdocs/includes/raven-js/example/file.min.js new file mode 100644 index 0000000000000000000000000000000000000000..12b9f811b28e00f123722e0c234438404fb07cb2 --- /dev/null +++ b/htdocs/includes/raven-js/example/file.min.js @@ -0,0 +1,2 @@ +function add(a,b){"use strict";return a+b}function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply(add(a,b),a,b)/c}catch(e){Raven.captureException(e)}} +//@ sourceMappingURL=file.sourcemap.js \ No newline at end of file diff --git a/htdocs/includes/raven-js/example/file.sourcemap.js b/htdocs/includes/raven-js/example/file.sourcemap.js new file mode 100644 index 0000000000000000000000000000000000000000..1bd0f6510cf4a715d1089ea7dc9118b4fc1cddf3 --- /dev/null +++ b/htdocs/includes/raven-js/example/file.sourcemap.js @@ -0,0 +1 @@ +{"version":3,"file":"file.min.js","sources":["file1.js","file2.js"],"names":["add","a","b","multiply","divide","c","e","Raven","captureException"],"mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC,ECFZ,QAASC,UAASF,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASE,QAAOH,EAAGC,GAClB,YACA,KACC,MAAOC,UAASH,IAAIC,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF"} \ No newline at end of file diff --git a/htdocs/includes/raven-js/example/file1.js b/htdocs/includes/raven-js/example/file1.js new file mode 100644 index 0000000000000000000000000000000000000000..eed5827852d32f4a2f3ee8602cfe38745151fc98 --- /dev/null +++ b/htdocs/includes/raven-js/example/file1.js @@ -0,0 +1,4 @@ +function add(a, b) { + "use strict"; + return a + b; +} \ No newline at end of file diff --git a/htdocs/includes/raven-js/example/file2.js b/htdocs/includes/raven-js/example/file2.js new file mode 100644 index 0000000000000000000000000000000000000000..8b1743568464e40124e86d3a6103b8ac1c689335 --- /dev/null +++ b/htdocs/includes/raven-js/example/file2.js @@ -0,0 +1,12 @@ +function multiply(a, b) { + "use strict"; + return a * b; +} +function divide(a, b) { + "use strict"; + try { + return multiply(add(a, b), a, b) / c; + } catch (e) { + Raven.captureException(e); + } +} diff --git a/htdocs/includes/raven-js/example/index.html b/htdocs/includes/raven-js/example/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b7ebbdd018eb5bc7322d02cc7b474efc42651f36 --- /dev/null +++ b/htdocs/includes/raven-js/example/index.html @@ -0,0 +1,41 @@ +<!doctype html> +<html> +<head> + <title>Scratch Disk</title> +</head> +<script src="../vendor/TraceKit/tracekit.js"></script> +<script src="../src/raven.js"></script> +<!-- <script src="scratch.min.js"></script> --> +<script src="scratch.js" crossorigin></script> +<script src="file.min.js" crossorigin></script> +<script> +//cool +//really cool +//awesome +Raven.config('http://50dbe04cd1224d439e9c49bf1d0464df@localhost:8000/1', { + whitelistUrls: [ + /localhost/ + ], + dataCallback: function(data) { + console.log(data); + return data; + } +}).install(); + +Raven.setUserContext({ + email: 'matt@ydekproductions.com', + id: 5 +}) + +</script> +<body> +<button id="test">Break me</button> +<script>ready()</script> + +<button onclick="divide(1, 0)">Sourcemap breakage</button> +<button onclick="derp()">window.onerror</button> +<button onclick="testOptions()">test options</button> +<button onclick="throwString()">throw string</button> + +</body> +</html> diff --git a/htdocs/includes/raven-js/example/scratch.js b/htdocs/includes/raven-js/example/scratch.js new file mode 100644 index 0000000000000000000000000000000000000000..f1962bbdfc18bb6bb3e765a2055f808b2e17be50 --- /dev/null +++ b/htdocs/includes/raven-js/example/scratch.js @@ -0,0 +1,42 @@ +function foo() { + console.log("lol, i don't do anything") +} + +function foo2() { + foo() + console.log('i called foo') +} + +function broken() { + try { + /*fkjdsahfdhskfhdsahfudshafuoidashfudsa*/ fdasfds[0]; // i throw an error h sadhf hadsfdsakf kl;dsjaklf jdklsajfk ljds;klafldsl fkhdas;hf hdsaf hdsalfhjldksahfljkdsahfjkl dhsajkfl hdklsahflkjdsahkfj hdsjakhf dkashfl diusafh kdsjahfkldsahf jkdashfj khdasjkfhdjksahflkjdhsakfhjdksahfjkdhsakf hdajskhf kjdash kjfads fjkadsh jkfdsa jkfdas jkfdjkas hfjkdsajlk fdsajk fjkdsa fjdsa fdkjlsa fjkdaslk hfjlkdsah fhdsahfui + }catch(e) { + Raven.captureException(e); + } +} + +function ready() { + document.getElementById('test').onclick = broken; +} + +function foo3() { + document.getElementById('crap').value = 'barfdasjkfhoadshflkaosfjadiosfhdaskjfasfadsfads'; +} + +function somethingelse() { + document.getElementById('somethingelse').value = 'this is some realy really long message just so our minification is largeeeeeeeeee!'; +} + +function derp() { + fdas[0]; +} + +function testOptions() { + Raven.context({tags: {foo: 'bar'}}, function() { + throw new Error('foo'); + }); +} + +function throwString() { + throw 'oops'; +} diff --git a/htdocs/includes/raven-js/package.json b/htdocs/includes/raven-js/package.json new file mode 100644 index 0000000000000000000000000000000000000000..9352edcab38251ed200d3eaaf75ebea44185eae7 --- /dev/null +++ b/htdocs/includes/raven-js/package.json @@ -0,0 +1,32 @@ +{ + "name": "raven-js", + "version": "1.1.19", + "license": "BSD-2-Clause", + "homepage": "https://getsentry.com", + "scripts": { + "pretest": "npm install", + "test": "grunt test" + }, + "repository": { + "type": "git", + "url": "git://github.com/getsentry/raven-js.git" + }, + "main": "dist/raven.js", + "devDependencies": { + "chai": "~1.8.1", + "grunt": "~0.4.1", + "grunt-cli": "~0.1.9", + "grunt-contrib-jshint": "~0.6.3", + "grunt-contrib-uglify": "~0.2.2", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-clean": "~0.4.0", + "grunt-mocha": "~0.4.1", + "grunt-release": "~0.6.0", + "grunt-s3": "~0.2.0-alpha.3", + "grunt-gitinfo": "~0.1.1", + "grunt-contrib-connect": "~0.5.0", + "grunt-contrib-copy": "~0.4.1", + "sinon": "~1.7.3", + "lodash": "~2.4.0" + } +} diff --git a/htdocs/includes/raven-js/plugins/angular.js b/htdocs/includes/raven-js/plugins/angular.js new file mode 100644 index 0000000000000000000000000000000000000000..98bf58920015958119dc963a0a34ef396fe77242 --- /dev/null +++ b/htdocs/includes/raven-js/plugins/angular.js @@ -0,0 +1,36 @@ +/** + * Angular.js plugin + * + * Provides an $exceptionHandler for Angular.js + */ +;(function(Raven, angular) { +'use strict'; + +// quit if angular isn't on the page +if (!angular) { + return; +} + +function ngRavenProvider($provide) { + $provide.decorator('$exceptionHandler', [ + 'RavenConfig', '$delegate', + ngRavenExceptionHandler + ]); +} + +function ngRavenExceptionHandler(RavenConfig, $delegate) { + if (!RavenConfig) + throw new Error('RavenConfig must be set before using this'); + + Raven.config(RavenConfig.dsn, RavenConfig.config).install(); + return function angularExceptionHandler(ex, cause) { + $delegate(ex, cause); + Raven.captureException(ex, {extra: {cause: cause}}); + }; +} + +angular.module('ngRaven', []) + .config(['$provide', ngRavenProvider]) + .value('Raven', Raven); + +})(window.Raven, window.angular); diff --git a/htdocs/includes/raven-js/plugins/backbone.js b/htdocs/includes/raven-js/plugins/backbone.js new file mode 100644 index 0000000000000000000000000000000000000000..09e0f6b8c1c9cbe573eb8f262debb20ff2721280 --- /dev/null +++ b/htdocs/includes/raven-js/plugins/backbone.js @@ -0,0 +1,55 @@ +/** + * Backbone.js plugin + * + * Patches Backbone.Events callbacks. + */ +;(function(window, Raven, Backbone) { +'use strict'; + +// quit if Backbone isn't on the page +if (!Backbone) { + return; +} + +function makeBackboneEventsOn(oldOn) { + return function BackboneEventsOn(name, callback, context) { + var wrapCallback = function (cb) { + if (Object.prototype.toString.call(cb) === '[object Function]') { + var _callback = cb._callback || cb; + cb = Raven.wrap(cb); + cb._callback = _callback; + } + return cb; + }; + if (Object.prototype.toString.call(name) === '[object Object]') { + // Handle event maps. + for (var key in name) { + if (name.hasOwnProperty(key)) { + name[key] = wrapCallback(name[key]); + } + } + } else { + callback = wrapCallback(callback); + } + return oldOn.call(this, name, callback, context); + }; +} + +// We're too late to catch all of these by simply patching Backbone.Events.on +var affectedObjects = [ + Backbone.Events, + Backbone, + Backbone.Model.prototype, + Backbone.Collection.prototype, + Backbone.View.prototype, + Backbone.Router.prototype, + Backbone.History.prototype +], i = 0, l = affectedObjects.length; + +for (; i < l; i++) { + var affected = affectedObjects[i]; + affected.on = makeBackboneEventsOn(affected.on); + affected.bind = affected.on; +} + +}(window, window.Raven, window.Backbone)); diff --git a/htdocs/includes/raven-js/plugins/console.js b/htdocs/includes/raven-js/plugins/console.js new file mode 100644 index 0000000000000000000000000000000000000000..4d5ba94090f4351652f84a44456b4967e1cc1fbf --- /dev/null +++ b/htdocs/includes/raven-js/plugins/console.js @@ -0,0 +1,43 @@ +/** + * console plugin + * + * Monkey patches console.* calls into Sentry messages with + * their appropriate log levels. (Experimental) + */ +;(function(window, Raven, console) { +'use strict'; + +var originalConsole = console, + logLevels = ['debug', 'info', 'warn', 'error'], + level = logLevels.pop(); + +var logForGivenLevel = function(level) { + var originalConsoleLevel = console[level]; + + // warning level is the only level that doesn't map up + // correctly with what Sentry expects. + if (level === 'warn') level = 'warning'; + return function () { + var args = [].slice.call(arguments); + Raven.captureMessage('' + args, {level: level, logger: 'console'}); + + // this fails for some browsers. :( + if (originalConsoleLevel) { + // IE9 doesn't allow calling apply on console functions directly + // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193 + Function.prototype.bind + .call(originalConsoleLevel, originalConsole) + .apply(originalConsole, args); + } + }; +}; + + +while(level) { + console[level] = logForGivenLevel(level); + level = logLevels.pop(); +} +// export +window.console = console; + +}(window, window.Raven, window.console || {})); diff --git a/htdocs/includes/raven-js/plugins/ember.js b/htdocs/includes/raven-js/plugins/ember.js new file mode 100644 index 0000000000000000000000000000000000000000..c6d0551c709063bc1236bc133f96364bfb44e63b --- /dev/null +++ b/htdocs/includes/raven-js/plugins/ember.js @@ -0,0 +1,29 @@ +/** + * Ember.js plugin + * + * Patches event handler callbacks and ajax callbacks. + */ +;(function(window, Raven, Ember) { +'use strict'; + +// quit if Ember isn't on the page +if (!Ember) { + return; +} + +var _oldOnError = Ember.onerror; +Ember.onerror = function EmberOnError(error) { + Raven.captureException(error); + if (typeof _oldOnError === 'function') { + _oldOnError.call(this, error); + } +}; +Ember.RSVP.on('error', function (reason) { + if (reason instanceof Error) { + Raven.captureException(reason, {extra: {context: 'Unhandled Promise error detected'}}); + } else { + Raven.captureMessage('Unhandled Promise error detected', {extra: {reason: reason}}); + } +}); + +}(window, window.Raven, window.Ember)); diff --git a/htdocs/includes/raven-js/plugins/jquery.js b/htdocs/includes/raven-js/plugins/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..4a5474cdb86db2517c28134a194d94e5680e3a20 --- /dev/null +++ b/htdocs/includes/raven-js/plugins/jquery.js @@ -0,0 +1,75 @@ +/** + * jQuery plugin + * + * Patches event handler callbacks and ajax callbacks. + */ +;(function(window, Raven, $) { +'use strict'; + +// quit if jQuery isn't on the page +if (!$) { + return; +} + +var _oldEventAdd = $.event.add; +$.event.add = function ravenEventAdd(elem, types, handler, data, selector) { + var _handler; + + if (handler && handler.handler) { + _handler = handler.handler; + handler.handler = Raven.wrap(handler.handler); + } else { + _handler = handler; + handler = Raven.wrap(handler); + } + + // If the handler we are attaching doesn’t have the same guid as + // the original, it will never be removed when someone tries to + // unbind the original function later. Technically as a result of + // this our guids are no longer globally unique, but whatever, that + // never hurt anybody RIGHT?! + if (_handler.guid) { + handler.guid = _handler.guid; + } else { + handler.guid = _handler.guid = $.guid++; + } + + return _oldEventAdd.call(this, elem, types, handler, data, selector); +}; + +var _oldReady = $.fn.ready; +$.fn.ready = function ravenjQueryReadyWrapper(fn) { + return _oldReady.call(this, Raven.wrap(fn)); +}; + +var _oldAjax = $.ajax; +$.ajax = function ravenAjaxWrapper(url, options) { + var keys = ['complete', 'error', 'success'], key; + + // Taken from https://github.com/jquery/jquery/blob/eee2eaf1d7a189d99106423a4206c224ebd5b848/src/ajax.js#L311-L318 + // If url is an object, simulate pre-1.5 signature + if (typeof url === 'object') { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + /*jshint -W084*/ + while(key = keys.pop()) { + if ($.isFunction(options[key])) { + options[key] = Raven.wrap(options[key]); + } + } + /*jshint +W084*/ + + try { + return _oldAjax.call(this, url, options); + } catch (e) { + Raven.captureException(e); + throw e; + } +}; + +}(window, window.Raven, window.jQuery)); diff --git a/htdocs/includes/raven-js/plugins/native.js b/htdocs/includes/raven-js/plugins/native.js new file mode 100644 index 0000000000000000000000000000000000000000..c641f13a5d1cf4e12073cd5405bd02d36ab31564 --- /dev/null +++ b/htdocs/includes/raven-js/plugins/native.js @@ -0,0 +1,33 @@ +/** + * native plugin + * + * Extends support for global error handling for asynchronous browser + * functions. Adopted from Closure Library's errorhandler.js. + */ +;(function extendToAsynchronousCallbacks(window, Raven) { +"use strict"; + +var _helper = function _helper(fnName) { + var originalFn = window[fnName]; + window[fnName] = function ravenAsyncExtension() { + // Make a copy of the arguments + var args = [].slice.call(arguments); + var originalCallback = args[0]; + if (typeof (originalCallback) === 'function') { + args[0] = Raven.wrap(originalCallback); + } + // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it + // also supports only two arguments and doesn't care what this is, so we + // can just call the original function directly. + if (originalFn.apply) { + return originalFn.apply(this, args); + } else { + return originalFn(args[0], args[1]); + } + }; +}; + +_helper('setTimeout'); +_helper('setInterval'); + +}(window, window.Raven)); diff --git a/htdocs/includes/raven-js/plugins/require.js b/htdocs/includes/raven-js/plugins/require.js new file mode 100644 index 0000000000000000000000000000000000000000..60378a1b0b9b10e0e541d7ca1877242406433c00 --- /dev/null +++ b/htdocs/includes/raven-js/plugins/require.js @@ -0,0 +1,14 @@ +/** + * require.js plugin + * + * Automatically wrap define/require callbacks. (Experimental) + */ +;(function(window, Raven) { +'use strict'; + +if (typeof define === 'function' && define.amd) { + window.define = Raven.wrap({deep: false}, define); + window.require = Raven.wrap({deep: false}, require); +} + +}(window, window.Raven)); diff --git a/htdocs/includes/raven-js/src/raven.js b/htdocs/includes/raven-js/src/raven.js new file mode 100644 index 0000000000000000000000000000000000000000..a2d2b35604b45d0c1008fca3794c1f04e253c6fe --- /dev/null +++ b/htdocs/includes/raven-js/src/raven.js @@ -0,0 +1,830 @@ +'use strict'; + +// First, check for JSON support +// If there is no JSON, we no-op the core features of Raven +// since JSON is required to encode the payload +var _Raven = window.Raven, + hasJSON = !!(typeof JSON === 'object' && JSON.stringify), + lastCapturedException, + lastEventId, + globalServer, + globalUser, + globalKey, + globalProject, + globalOptions = { + logger: 'javascript', + ignoreErrors: [], + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + collectWindowErrors: true, + tags: {}, + maxMessageLength: 100, + extra: {} + }, + authQueryString, + isRavenInstalled = false, + + objectPrototype = Object.prototype, + startTime = now(); + +/* + * The core Raven singleton + * + * @this {Raven} + */ +var Raven = { + VERSION: '<%= pkg.version %>', + + debug: true, + + /* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ + noConflict: function() { + window.Raven = _Raven; + return Raven; + }, + + /* + * Configure Raven with a DSN and extra options + * + * @param {string} dsn The public Sentry DSN + * @param {object} options Optional set of of global options [optional] + * @return {Raven} + */ + config: function(dsn, options) { + if (globalServer) { + logDebug('error', 'Error: Raven has already been configured'); + return Raven; + } + if (!dsn) return Raven; + + var uri = parseDSN(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); + + // merge in options + if (options) { + each(options, function(key, value){ + globalOptions[key] = value; + }); + } + + // "Script error." is hard coded into browsers for errors that it can't read. + // this is the result of a script being pulled in from an external domain and CORS. + globalOptions.ignoreErrors.push(/^Script error\.?$/); + globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/); + + // join regexp rules into one big rule + globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors); + globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false; + globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false; + globalOptions.includePaths = joinRegExp(globalOptions.includePaths); + + globalKey = uri.user; + globalProject = uri.path.substr(lastSlash + 1); + + // assemble the endpoint from the uri pieces + globalServer = '//' + uri.host + + (uri.port ? ':' + uri.port : '') + + '/' + path + 'api/' + globalProject + '/store/'; + + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + + if (globalOptions.fetchContext) { + TraceKit.remoteFetching = true; + } + + if (globalOptions.linesOfContext) { + TraceKit.linesOfContext = globalOptions.linesOfContext; + } + + TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; + + setAuthQueryString(); + + // return for chaining + return Raven; + }, + + /* + * Installs a global window.onerror error handler + * to capture and report uncaught exceptions. + * At this point, install() is required to be called due + * to the way TraceKit is set up. + * + * @return {Raven} + */ + install: function() { + if (isSetup() && !isRavenInstalled) { + TraceKit.report.subscribe(handleStackInfo); + isRavenInstalled = true; + } + + return Raven; + }, + + /* + * Wrap code within a context so Raven can capture errors + * reliably across domains that is executed immediately. + * + * @param {object} options A specific set of options for this context [optional] + * @param {function} func The callback to be immediately executed within the context + * @param {array} args An array of arguments to be called with the callback [optional] + */ + context: function(options, func, args) { + if (isFunction(options)) { + args = func || []; + func = options; + options = undefined; + } + + return Raven.wrap(options, func).apply(this, args); + }, + + /* + * Wrap code within a context and returns back a new function to be executed + * + * @param {object} options A specific set of options for this context [optional] + * @param {function} func The function to be wrapped in a new context + * @return {function} The newly wrapped functions with a context + */ + wrap: function(options, func) { + // 1 argument has been passed, and it's not a function + // so just return it + if (isUndefined(func) && !isFunction(options)) { + return options; + } + + // options is optional + if (isFunction(options)) { + func = options; + options = undefined; + } + + // At this point, we've passed along 2 arguments, and the second one + // is not a function either, so we'll just return the second argument. + if (!isFunction(func)) { + return func; + } + + // We don't wanna wrap it twice! + if (func.__raven__) { + return func; + } + + function wrapped() { + var args = [], i = arguments.length, + deep = !options || options && options.deep !== false; + // Recursively wrap all of a function's arguments that are + // functions themselves. + + while(i--) args[i] = deep ? Raven.wrap(options, arguments[i]) : arguments[i]; + + try { + /*jshint -W040*/ + return func.apply(this, args); + } catch(e) { + Raven.captureException(e, options); + throw e; + } + } + + // copy over properties of the old function + for (var property in func) { + if (hasKey(func, property)) { + wrapped[property] = func[property]; + } + } + + // Signal that this function has been wrapped already + // for both debugging and to prevent it to being wrapped twice + wrapped.__raven__ = true; + wrapped.__inner__ = func; + + return wrapped; + }, + + /* + * Uninstalls the global error handler. + * + * @return {Raven} + */ + uninstall: function() { + TraceKit.report.uninstall(); + isRavenInstalled = false; + + return Raven; + }, + + /* + * Manually capture an exception and send it over to Sentry + * + * @param {error} ex An exception to be logged + * @param {object} options A specific set of options for this error [optional] + * @return {Raven} + */ + captureException: function(ex, options) { + // If not an Error is passed through, recall as a message instead + if (!isError(ex)) return Raven.captureMessage(ex, options); + + // Store the raw exception object for potential debugging and introspection + lastCapturedException = ex; + + // TraceKit.report will re-raise any exception passed to it, + // which means you have to wrap it in try/catch. Instead, we + // can wrap it here and only re-raise if TraceKit.report + // raises an exception different from the one we asked to + // report on. + try { + TraceKit.report(ex, options); + } catch(ex1) { + if(ex !== ex1) { + throw ex1; + } + } + + return Raven; + }, + + /* + * Manually send a message to Sentry + * + * @param {string} msg A plain message to be captured in Sentry + * @param {object} options A specific set of options for this message [optional] + * @return {Raven} + */ + captureMessage: function(msg, options) { + // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an + // early call; we'll error on the side of logging anything called before configuration since it's + // probably something you should see: + if (!!globalOptions.ignoreErrors.test && globalOptions.ignoreErrors.test(msg)) { + return; + } + + // Fire away! + send( + objectMerge({ + message: msg + '' // Make sure it's actually a string + }, options) + ); + + return Raven; + }, + + /* + * Set/clear a user to be sent along with the payload. + * + * @param {object} user An object representing user data [optional] + * @return {Raven} + */ + setUserContext: function(user) { + globalUser = user; + + return Raven; + }, + + /* + * Set extra attributes to be sent along with the payload. + * + * @param {object} extra An object representing extra data [optional] + * @return {Raven} + */ + setExtraContext: function(extra) { + globalOptions.extra = extra || {}; + + return Raven; + }, + + /* + * Set tags to be sent along with the payload. + * + * @param {object} tags An object representing tags [optional] + * @return {Raven} + */ + setTagsContext: function(tags) { + globalOptions.tags = tags || {}; + + return Raven; + }, + + /* + * Set release version of application + * + * @param {string} release Typically something like a git SHA to identify version + * @return {Raven} + */ + setReleaseContext: function(release) { + globalOptions.release = release; + + return Raven; + }, + + /* + * Set the dataCallback option + * + * @param {function} callback The callback to run which allows the + * data blob to be mutated before sending + * @return {Raven} + */ + setDataCallback: function(callback) { + globalOptions.dataCallback = callback; + + return Raven; + }, + + /* + * Set the shouldSendCallback option + * + * @param {function} callback The callback to run which allows + * introspecting the blob before sending + * @return {Raven} + */ + setShouldSendCallback: function(callback) { + globalOptions.shouldSendCallback = callback; + + return Raven; + }, + + /* + * Get the latest raw exception that was captured by Raven. + * + * @return {error} + */ + lastException: function() { + return lastCapturedException; + }, + + /* + * Get the last event id + * + * @return {string} + */ + lastEventId: function() { + return lastEventId; + }, + + /* + * Determine if Raven is setup and ready to go. + * + * @return {boolean} + */ + isSetup: function() { + return isSetup(); + } +}; + +Raven.setUser = Raven.setUserContext; // To be deprecated + +function triggerEvent(eventType, options) { + var event, key; + + options = options || {}; + + eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventType, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventType; + } + + for (key in options) if (hasKey(options, key)) { + event[key] = options[key]; + } + + if (document.createEvent) { + // IE9 if standards + document.dispatchEvent(event); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + document.fireEvent('on' + event.eventType.toLowerCase(), event); + } catch(e) {} + } +} + +var dsnKeys = 'source protocol user pass host port path'.split(' '), + dsnPattern = /^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/; + +function RavenConfigError(message) { + this.name = 'RavenConfigError'; + this.message = message; +} +RavenConfigError.prototype = new Error(); +RavenConfigError.prototype.constructor = RavenConfigError; + +/**** Private functions ****/ +function parseDSN(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; + + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch(e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } + + if (dsn.pass) + throw new RavenConfigError('Do not specify your private key in the DSN!'); + + return dsn; +} + +function isUndefined(what) { + return what === void 0; +} + +function isFunction(what) { + return typeof what === 'function'; +} + +function isString(what) { + return objectPrototype.toString.call(what) === '[object String]'; +} + +function isObject(what) { + return typeof what === 'object' && what !== null; +} + +function isEmptyObject(what) { + for (var k in what) return false; + return true; +} + +// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 +// with some tiny modifications +function isError(what) { + return isObject(what) && + objectPrototype.toString.call(what) === '[object Error]' || + what instanceof Error; +} + +/** + * hasKey, a better form of hasOwnProperty + * Example: hasKey(MainHostObject, property) === true/false + * + * @param {Object} host object to check property + * @param {string} key to check + */ +function hasKey(object, key) { + return objectPrototype.hasOwnProperty.call(object, key); +} + +function each(obj, callback) { + var i, j; + + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } +} + + +function setAuthQueryString() { + authQueryString = + '?sentry_version=4' + + '&sentry_client=raven-js/' + Raven.VERSION + + '&sentry_key=' + globalKey; +} + + +function handleStackInfo(stackInfo, options) { + var frames = []; + + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = normalizeFrame(stack); + if (frame) { + frames.push(frame); + } + }); + } + + triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + + processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); +} + +function normalizeFrame(frame) { + if (!frame.url) return; + + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + 'function': frame.func || '?' + }, context = extractContextFromFrame(frame), i; + + if (context) { + var keys = ['pre_context', 'context_line', 'post_context']; + i = 3; + while (i--) normalized[keys[i]] = context[i]; + } + + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !globalOptions.includePaths.test(normalized.filename) || + // Now we check for fun, if the function name is Raven or TraceKit + /(Raven|TraceKit)\./.test(normalized['function']) || + // finally, we do a last ditch effort and check for raven.min.js + /raven\.(min\.)?js$/.test(normalized.filename) + ); + + return normalized; +} + +function extractContextFromFrame(frame) { + // immediately check if we should even attempt to parse a context + if (!frame.context || !globalOptions.fetchContext) return; + + var context = frame.context, + pivot = ~~(context.length / 2), + i = context.length, isMinified = false; + + while (i--) { + // We're making a guess to see if the source is minified or not. + // To do that, we make the assumption if *any* of the lines passed + // in are greater than 300 characters long, we bail. + // Sentry will see that there isn't a context + if (context[i].length > 300) { + isMinified = true; + break; + } + } + + if (isMinified) { + // The source is minified and we don't know which column. Fuck it. + if (isUndefined(frame.column)) return; + + // If the source is minified and has a frame column + // we take a chunk of the offending line to hopefully shed some light + return [ + [], // no pre_context + context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column + [] // no post_context + ]; + } + + return [ + context.slice(0, pivot), // pre_context + context[pivot], // context_line + context.slice(pivot + 1) // post_context + ]; +} + +function processException(type, message, fileurl, lineno, frames, options) { + var stacktrace, label, i; + + // In some instances message is not actually a string, no idea why, + // so we want to always coerce it to one. + message += ''; + + // Sometimes an exception is getting logged in Sentry as + // <no message value> + // This can only mean that the message was falsey since this value + // is hardcoded into Sentry itself. + // At this point, if the message is falsey, we bail since it's useless + if (type === 'Error' && !message) return; + + if (globalOptions.ignoreErrors.test(message)) return; + + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); + stacktrace = {frames: frames}; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] + }; + } + + // Truncate the message to a max of characters + message = truncate(message, globalOptions.maxMessageLength); + + if (globalOptions.ignoreUrls && globalOptions.ignoreUrls.test(fileurl)) return; + if (globalOptions.whitelistUrls && !globalOptions.whitelistUrls.test(fileurl)) return; + + label = lineno ? message + ' at ' + lineno : message; + + // Fire away! + send( + objectMerge({ + // sentry.interfaces.Exception + exception: { + type: type, + value: message + }, + // sentry.interfaces.Stacktrace + stacktrace: stacktrace, + culprit: fileurl, + message: label + }, options) + ); +} + +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value){ + obj1[key] = value; + }); + return obj1; +} + +function truncate(str, max) { + return str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + +function now() { + return +new Date(); +} + +function getHttpData() { + var http = { + url: document.location.href, + headers: { + 'User-Agent': navigator.userAgent + } + }; + + if (document.referrer) { + http.headers.Referer = document.referrer; + } + + return http; +} + +function send(data) { + if (!isSetup()) return; + + data = objectMerge({ + project: globalProject, + logger: globalOptions.logger, + platform: 'javascript', + // sentry.interfaces.Http + request: getHttpData() + }, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(objectMerge({}, globalOptions.tags), data.tags); + data.extra = objectMerge(objectMerge({}, globalOptions.extra), data.extra); + + // Send along our own collected metadata with extra + data.extra = objectMerge({ + 'session:duration': now() - startTime + }, data.extra); + + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; + + if (globalUser) { + // sentry.interfaces.User + data.user = globalUser; + } + + // Include the release iff it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; + + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } + + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } + + // Check if the request should be filtered or not + if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { + return; + } + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + lastEventId = data.event_id || (data.event_id = uuid4()); + + makeRequest(data); +} + + +function makeRequest(data) { + var img = newImage(), + src = globalServer + authQueryString + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); + + img.crossOrigin = 'anonymous'; + img.onload = function success() { + triggerEvent('success', { + data: data, + src: src + }); + }; + img.onerror = img.onabort = function failure() { + triggerEvent('failure', { + data: data, + src: src + }); + }; + img.src = src; +} + +// Note: this is shitty, but I can't figure out how to get +// sinon to stub document.createElement without breaking everything +// so this wrapper is just so I can stub it for tests. +function newImage() { + return document.createElement('img'); +} + +function isSetup() { + if (!hasJSON) return false; // needs JSON support + if (!globalServer) { + logDebug('error', 'Error: Raven has not been configured.'); + return false; + } + return true; +} + +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 +function uuid4() { + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, + v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); +} + +function logDebug(level, message) { + if (window.console && console[level] && Raven.debug) { + console[level](message); + } +} + +function afterLoad() { + // Attempt to initialize Raven on load + var RavenConfig = window.RavenConfig; + if (RavenConfig) { + Raven.config(RavenConfig.dsn, RavenConfig.config).install(); + } +} +afterLoad(); diff --git a/htdocs/includes/raven-js/template/_copyright.js b/htdocs/includes/raven-js/template/_copyright.js new file mode 100644 index 0000000000000000000000000000000000000000..f1ee87084f875e0b25cfd47ec74e2bf6a2f0a0ac --- /dev/null +++ b/htdocs/includes/raven-js/template/_copyright.js @@ -0,0 +1,11 @@ +/*! Raven.js <%= pkg.release %> (<%= gitinfo.local.branch.current.shortSHA %>) | github.com/getsentry/raven-js */ + +/* + * Includes TraceKit + * https://github.com/getsentry/TraceKit + * + * Copyright <%= grunt.template.today('yyyy') %> Matt Robenolt and other contributors + * Released under the BSD license + * https://github.com/getsentry/raven-js/blob/master/LICENSE + * + */ diff --git a/htdocs/includes/raven-js/template/_footer.js b/htdocs/includes/raven-js/template/_footer.js new file mode 100644 index 0000000000000000000000000000000000000000..9d102d2458d48814d8e1fa78a94f0f2855db2dae --- /dev/null +++ b/htdocs/includes/raven-js/template/_footer.js @@ -0,0 +1,19 @@ +// Expose Raven to the world +if (typeof define === 'function' && define.amd) { + // AMD + window.Raven = Raven; + define('raven', [], function() { + return Raven; + }); +} else if (typeof module === 'object') { + // browserify + module.exports = Raven; +} else if (typeof exports === 'object') { + // CommonJS + exports = Raven; +} else { + // Everything else + window.Raven = Raven; +} + +})(typeof window !== 'undefined' ? window : this); diff --git a/htdocs/includes/raven-js/template/_header.js b/htdocs/includes/raven-js/template/_header.js new file mode 100644 index 0000000000000000000000000000000000000000..27595a61a2db55a3cf651465c257cfba078d1a94 --- /dev/null +++ b/htdocs/includes/raven-js/template/_header.js @@ -0,0 +1,2 @@ +;(function(window, undefined){ +'use strict'; diff --git a/htdocs/includes/raven-js/test/index.html b/htdocs/includes/raven-js/test/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b5188ed060004e689c67bd9f0e588af57f690a4d --- /dev/null +++ b/htdocs/includes/raven-js/test/index.html @@ -0,0 +1,62 @@ +<!doctype html> +<html> + <head> + <title>Raven.js Test Suite</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="../node_modules/grunt-mocha/node_modules/mocha/mocha.css" /> + </head> + <body> + <div id="mocha"></div> + <!-- Mocha --> + <script src="../node_modules/grunt-mocha/node_modules/mocha/mocha.js"></script> + <script src="../node_modules/chai/chai.js"></script> + <script> + mocha.ui('bdd'); + mocha.reporter('html'); + var assert = chai.assert + </script> + + <!-- Mocking --> + <script src="../node_modules/sinon/pkg/sinon.js"></script> + <script> + beforeEach(function() { + this.sinon = sinon.sandbox.create(); + }); + + afterEach(function() { + this.sinon.restore(); + }); + </script> + + <!-- Raven --> + <script src="../vendor/TraceKit/tracekit.js"></script> + <script src="../src/raven.js"></script> + + <!-- Tests --> + <script src="raven.test.js"></script> + + <script> + if (!window.PHANTOMJS) { + (function(runner){ + var failed = []; + + runner.on('fail', function(test, err){ + failed.push({ + title: test.title, + fullTitle: test.fullTitle(), + error: { + message: err.message, + stack: err.stack + } + }); + }); + + runner.on('end', function(){ + runner.stats.failed = failed; + global.mochaResults = runner.stats; + }); + })(mocha.run()); + } + </script> + </body> +</html> diff --git a/htdocs/includes/raven-js/test/raven.test.js b/htdocs/includes/raven-js/test/raven.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e26a36ef174338461161b7c45bce684f0ad9c0ae --- /dev/null +++ b/htdocs/includes/raven-js/test/raven.test.js @@ -0,0 +1,1773 @@ +function flushRavenState() { + authQueryString = undefined; + hasJSON = !isUndefined(window.JSON); + lastCapturedException = undefined; + lastEventId = undefined; + globalServer = undefined; + globalUser = undefined; + globalProject = undefined; + globalOptions = { + logger: 'javascript', + release: undefined, + ignoreErrors: [], + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + collectWindowErrors: true, + maxMessageLength: 100, + tags: {}, + extra: {} + }, + startTime = 0 + ; + + Raven.uninstall(); +} + +// window.console must be stubbed in for browsers that don't have it +if (typeof window.console === 'undefined') { + console = {error: function(){}}; +} + +var SENTRY_DSN = 'http://abc@example.com:80/2'; + +function setupRaven() { + Raven.config(SENTRY_DSN); +} + +// patched to return a predictable result +function uuid4() { + return 'abc123'; +} + +// patched to be predictable +function now() { + return 100; +} + +describe('TraceKit', function(){ + describe('stacktrace info', function() { + it('should not remove anonymous functions from the stack', function() { + // mock up an error object with a stack trace that includes both + // named functions and anonymous functions + var stack_str = "" + + " Error: \n" + + " at new <anonymous> (http://example.com/js/test.js:63)\n" + // stack[0] + " at namedFunc0 (http://example.com/js/script.js:10)\n" + // stack[1] + " at http://example.com/js/test.js:65\n" + // stack[2] + " at namedFunc2 (http://example.com/js/script.js:20)\n" + // stack[3] + " at http://example.com/js/test.js:67\n" + // stack[4] + " at namedFunc4 (http://example.com/js/script.js:100001)"; // stack[5] + var mock_err = { stack: stack_str }; + var trace = TraceKit.computeStackTrace.computeStackTraceFromStackProp(mock_err); + + // Make sure TraceKit didn't remove the anonymous functions + // from the stack like it used to :) + assert.equal(trace.stack[0].func, 'new <anonymous>'); + assert.equal(trace.stack[1].func, 'namedFunc0'); + assert.equal(trace.stack[2].func, '?'); + assert.equal(trace.stack[3].func, 'namedFunc2'); + assert.equal(trace.stack[4].func, '?'); + assert.equal(trace.stack[5].func, 'namedFunc4'); + }); + }); + describe('error notifications', function(){ + var testMessage = "__mocha_ignore__"; + var subscriptionHandler; + // TraceKit waits 2000ms for window.onerror to fire, so give the tests + // some extra time. + this.timeout(3000); + + before(function() { + // Prevent the onerror call that's part of our tests from getting to + // mocha's handler, which would treat it as a test failure. + // + // We set this up here and don't ever restore the old handler, because + // we can't do that without clobbering TraceKit's handler, which can only + // be installed once. + var oldOnError = window.onerror; + window.onerror = function(message) { + if (message == testMessage) { + return true; + } + return oldOnError.apply(this, arguments); + }; + }); + + afterEach(function() { + if (subscriptionHandler) { + TraceKit.report.unsubscribe(subscriptionHandler); + subscriptionHandler = null; + } + }); + + function testErrorNotification(collectWindowErrors, callOnError, numReports, done) { + var extraVal = "foo"; + var numDone = 0; + // TraceKit's collectWindowErrors flag shouldn't affect direct calls + // to TraceKit.report, so we parameterize it for the tests. + TraceKit.collectWindowErrors = collectWindowErrors; + + subscriptionHandler = function(stackInfo, extra) { + assert.equal(extra, extraVal); + numDone++; + if (numDone == numReports) { + done(); + } + }; + TraceKit.report.subscribe(subscriptionHandler); + + // TraceKit.report always throws an exception in order to trigger + // window.onerror so it can gather more stack data. Mocha treats + // uncaught exceptions as errors, so we catch it via assert.throws + // here (and manually call window.onerror later if appropriate). + // + // We test multiple reports because TraceKit has special logic for when + // report() is called a second time before either a timeout elapses or + // window.onerror is called (which is why we always call window.onerror + // only once below, after all calls to report()). + for (var i=0; i < numReports; i++) { + var e = new Error('testing'); + assert.throws(function() { + TraceKit.report(e, extraVal); + }, e); + } + // The call to report should work whether or not window.onerror is + // triggered, so we parameterize it for the tests. We only call it + // once, regardless of numReports, because the case we want to test for + // multiple reports is when window.onerror is *not* called between them. + if (callOnError) { + window.onerror(testMessage); + } + } + + Mocha.utils.forEach([false, true], function(collectWindowErrors) { + Mocha.utils.forEach([false, true], function(callOnError) { + Mocha.utils.forEach([1, 2], function(numReports) { + it('it should receive arguments from report() when' + + ' collectWindowErrors is ' + collectWindowErrors + + ' and callOnError is ' + callOnError + + ' and numReports is ' + numReports, function(done) { + testErrorNotification(collectWindowErrors, callOnError, numReports, done); + }); + }); + }); + }); + }); +}); + +describe('globals', function() { + beforeEach(function() { + setupRaven(); + globalOptions.fetchContext = true; + }); + + afterEach(function() { + flushRavenState(); + }); + + describe('getHttpData', function() { + var data = getHttpData(); + + it('should have a url', function() { + assert.equal(data.url, window.location.href); + }); + + it('should have the user-agent header', function() { + assert.equal(data.headers['User-Agent'], navigator.userAgent); + }); + + it('should have referer header when available', function() { + // lol this test is awful + if (window.document.referrer) { + assert.equal(data.headers.Referer, window.document.referrer); + } else { + assert.isUndefined(data.headers.Referer); + } + }); + + }); + + describe('isUndefined', function() { + it('should do as advertised', function() { + assert.isTrue(isUndefined()); + assert.isFalse(isUndefined({})); + assert.isFalse(isUndefined('')); + assert.isTrue(isUndefined(undefined)); + }); + }); + + describe('isFunction', function() { + it('should do as advertised', function() { + assert.isTrue(isFunction(function(){})); + assert.isFalse(isFunction({})); + assert.isFalse(isFunction('')); + assert.isFalse(isFunction(undefined)); + }); + }); + + describe('isString', function() { + it('should do as advertised', function() { + assert.isTrue(isString('')); + assert.isTrue(isString(String(''))); + assert.isTrue(isString(new String(''))); + assert.isFalse(isString({})); + assert.isFalse(isString(undefined)); + assert.isFalse(isString(function(){})); + }); + }); + + describe('isObject', function() { + it('should do as advertised', function() { + assert.isTrue(isObject({})); + assert.isTrue(isObject(new Error())) + assert.isFalse(isObject('')); + }); + }); + + describe('isEmptyObject', function() { + it('should work as advertised', function() { + assert.isTrue(isEmptyObject({})); + assert.isFalse(isEmptyObject({foo: 1})); + }); + }); + + describe('isError', function() { + it('should work as advertised', function() { + assert.isTrue(isError(new Error())); + assert.isTrue(isError(new ReferenceError())); + assert.isTrue(isError(new RavenConfigError())); + assert.isFalse(isError({})); + assert.isFalse(isError('')); + assert.isFalse(isError(true)); + }); + }); + + describe('objectMerge', function() { + it('should work as advertised', function() { + assert.deepEqual(objectMerge({}, {}), {}); + assert.deepEqual(objectMerge({a:1}, {b:2}), {a:1, b:2}); + assert.deepEqual(objectMerge({a:1}), {a:1}); + }); + }); + + describe('truncate', function() { + it('should work as advertised', function() { + assert.equal(truncate('lolol', 3), 'lol\u2026'); + assert.equal(truncate('lolol', 10), 'lolol'); + assert.equal(truncate('lol', 3), 'lol'); + }); + }); + + describe('isSetup', function() { + it('should return false with no JSON support', function() { + globalServer = 'http://localhost/'; + hasJSON = false; + assert.isFalse(isSetup()); + }); + + it('should return false when Raven is not configured', function() { + hasJSON = true; // be explicit + globalServer = undefined; + this.sinon.stub(window, 'logDebug'); + assert.isFalse(isSetup()); + }); + + it('should return true when everything is all gravy', function() { + hasJSON = true; + assert.isTrue(isSetup()); + }); + }); + + describe('logDebug', function() { + var level = 'error', + message = 'foobar'; + + it('should not write to console when Raven.debug is false', function() { + Raven.debug = false; + this.sinon.stub(console, level); + logDebug(level, message); + assert.isFalse(console[level].called); + }); + + it('should write to console when Raven.debug is true', function() { + Raven.debug = true; + this.sinon.stub(console, level); + logDebug(level, message); + assert.isTrue(console[level].calledOnce); + }); + }); + + describe('setAuthQueryString', function() { + it('should return a properly formatted string and cache it', function() { + var expected = '?sentry_version=4&sentry_client=raven-js/<%= pkg.version %>&sentry_key=abc'; + setAuthQueryString(); + assert.strictEqual(authQueryString, expected); + }); + }); + + describe('parseDSN', function() { + it('should do what it advertises', function() { + var pieces = parseDSN('http://abc@example.com:80/2'); + assert.strictEqual(pieces.protocol, 'http'); + assert.strictEqual(pieces.user, 'abc'); + assert.strictEqual(pieces.port, '80'); + assert.strictEqual(pieces.path, '/2'); + assert.strictEqual(pieces.host, 'example.com'); + }); + + it('should parse protocol relative', function() { + var pieces = parseDSN('//user@mattrobenolt.com/'); + assert.strictEqual(pieces.protocol, ''); + assert.strictEqual(pieces.user, 'user'); + assert.strictEqual(pieces.port, ''); + assert.strictEqual(pieces.path, '/'); + assert.strictEqual(pieces.host, 'mattrobenolt.com'); + }); + + it('should parse domain with hyphen', function() { + var pieces = parseDSN('http://user@matt-robenolt.com/1'); + assert.strictEqual(pieces.protocol, 'http'); + assert.strictEqual(pieces.user, 'user'); + assert.strictEqual(pieces.port, ''); + assert.strictEqual(pieces.path, '/1'); + assert.strictEqual(pieces.host, 'matt-robenolt.com'); + }); + + it('should raise a RavenConfigError when setting a password', function() { + try { + parseDSN('http://user:pass@example.com/2'); + } catch(e) { + return assert.equal(e.name, 'RavenConfigError'); + } + // shouldn't hit this + assert.isTrue(false); + }); + + it('should raise a RavenConfigError with an invalid DSN', function() { + try { + parseDSN('lol'); + } catch(e) { + return assert.equal(e.name, 'RavenConfigError'); + } + // shouldn't hit this + assert.isTrue(false); + }); + }); + + describe('normalizeFrame', function() { + it('should handle a normal frame', function() { + var context = [ + ['line1'], // pre + 'line2', // culprit + ['line3'] // post + ]; + this.sinon.stub(window, 'extractContextFromFrame').returns(context); + var frame = { + url: 'http://example.com/path/file.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + globalOptions.fetchContext = true; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://example.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'lol', + pre_context: ['line1'], + context_line: 'line2', + post_context: ['line3'], + in_app: true + }); + }); + + it('should handle a frame without context', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://example.com/path/file.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + globalOptions.fetchContext = true; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://example.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'lol', + in_app: true + }); + }); + + it('should not mark `in_app` if rules match', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://example.com/path/file.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + globalOptions.fetchContext = true; + globalOptions.includePaths = /^http:\/\/example\.com/; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://example.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'lol', + in_app: true + }); + }); + + it('should mark `in_app` if rules do not match', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://lol.com/path/file.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + globalOptions.fetchContext = true; + globalOptions.includePaths = /^http:\/\/example\.com/; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://lol.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'lol', + in_app: false + }); + }); + + it('should mark `in_app` for raven.js', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://lol.com/path/raven.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://lol.com/path/raven.js', + lineno: 10, + colno: 11, + 'function': 'lol', + in_app: false + }); + }); + + it('should mark `in_app` for raven.min.js', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://lol.com/path/raven.min.js', + line: 10, + column: 11, + func: 'lol' + // context: [] context is stubbed + }; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://lol.com/path/raven.min.js', + lineno: 10, + colno: 11, + 'function': 'lol', + in_app: false + }); + }); + + it('should mark `in_app` for Raven', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://lol.com/path/file.js', + line: 10, + column: 11, + func: 'Raven.wrap' + // context: [] context is stubbed + }; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://lol.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'Raven.wrap', + in_app: false + }); + }); + + it('should mark `in_app` for TraceKit', function() { + this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + var frame = { + url: 'http://lol.com/path/file.js', + line: 10, + column: 11, + func: 'TraceKit.lol' + // context: [] context is stubbed + }; + + assert.deepEqual(normalizeFrame(frame), { + filename: 'http://lol.com/path/file.js', + lineno: 10, + colno: 11, + 'function': 'TraceKit.lol', + in_app: false + }); + }); + }); + + describe('extractContextFromFrame', function() { + it('should handle a normal frame', function() { + var frame = { + column: 2, + context: [ + 'line1', + 'line2', + 'line3', + 'line4', + 'line5', + 'culprit', + 'line7', + 'line8', + 'line9', + 'line10', + 'line11' + ] + }; + var context = extractContextFromFrame(frame); + assert.deepEqual(context, [ + ['line1', 'line2', 'line3', 'line4', 'line5'], + 'culprit', + ['line7', 'line8', 'line9', 'line10', 'line11'] + ]); + }); + + it('should return nothing if there is no context', function() { + var frame = { + column: 2 + }; + assert.isUndefined(extractContextFromFrame(frame)); + }); + + it('should reject a context if a line is too long without a column', function() { + var frame = { + context: [ + new Array(1000).join('f') // generate a line that is 1000 chars long + ] + }; + assert.isUndefined(extractContextFromFrame(frame)); + }); + + it('should reject a minified context with fetchContext disabled', function() { + var frame = { + column: 2, + context: [ + 'line1', + 'line2', + 'line3', + 'line4', + 'line5', + 'culprit', + 'line7', + 'line8', + 'line9', + 'line10', + 'line11' + ] + }; + globalOptions.fetchContext = false; + assert.isUndefined(extractContextFromFrame(frame)); + }); + + it('should truncate the minified line if there is a column number without sourcemaps enabled', function() { + // Note to future self: + // Array(51).join('f').length === 50 + var frame = { + column: 2, + context: [ + 'aa' + (new Array(51).join('f')) + (new Array(500).join('z')) + ] + }; + assert.deepEqual(extractContextFromFrame(frame), [[], new Array(51).join('f'), []]); + }); + }); + + describe('processException', function() { + it('should respect `ignoreErrors`', function() { + this.sinon.stub(window, 'send'); + + globalOptions.ignoreErrors = joinRegExp(['e1', 'e2']); + processException('Error', 'e1', 'http://example.com', []); + assert.isFalse(window.send.called); + processException('Error', 'e2', 'http://example.com', []); + assert.isFalse(window.send.called); + processException('Error', 'error', 'http://example.com', []); + assert.isTrue(window.send.calledOnce); + }); + + it('should respect `ignoreUrls`', function() { + this.sinon.stub(window, 'send'); + + globalOptions.ignoreUrls = joinRegExp([/.+?host1.+/, /.+?host2.+/]); + processException('Error', 'error', 'http://host1/', []); + assert.isFalse(window.send.called); + processException('Error', 'error', 'http://host2/', []); + assert.isFalse(window.send.called); + processException('Error', 'error', 'http://host3/', []); + assert.isTrue(window.send.calledOnce); + }); + + it('should respect `whitelistUrls`', function() { + this.sinon.stub(window, 'send'); + + globalOptions.whitelistUrls = joinRegExp([/.+?host1.+/, /.+?host2.+/]); + processException('Error', 'error', 'http://host1/', []); + assert.equal(window.send.callCount, 1); + processException('Error', 'error', 'http://host2/', []); + assert.equal(window.send.callCount, 2); + processException('Error', 'error', 'http://host3/', []); + assert.equal(window.send.callCount, 2); + }); + + it('should send a proper payload with frames', function() { + this.sinon.stub(window, 'send'); + + var frames = [ + { + filename: 'http://example.com/file1.js' + }, + { + filename: 'http://example.com/file2.js' + } + ], framesFlipped = frames.slice(0); + + framesFlipped.reverse(); + + processException('Error', 'lol', 'http://example.com/override.js', 10, frames.slice(0), {}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: framesFlipped + }, + culprit: 'http://example.com/file1.js', + message: 'lol at 10' + }]); + + processException('Error', 'lol', '', 10, frames.slice(0), {}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: framesFlipped + }, + culprit: 'http://example.com/file1.js', + message: 'lol at 10' + }]); + + processException('Error', 'lol', '', 10, frames.slice(0), {extra: 'awesome'}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: framesFlipped + }, + culprit: 'http://example.com/file1.js', + message: 'lol at 10', + extra: 'awesome' + }]); + }); + + it('should send a proper payload without frames', function() { + this.sinon.stub(window, 'send'); + + processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com/override.js', + lineno: 10, + in_app: true + }] + }, + culprit: 'http://example.com/override.js', + message: 'lol at 10' + }]); + + processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com/override.js', + lineno: 10, + in_app: true + }] + }, + culprit: 'http://example.com/override.js', + message: 'lol at 10' + }]); + + processException('Error', 'lol', 'http://example.com/override.js', 10, [], {extra: 'awesome'}); + assert.deepEqual(window.send.lastCall.args, [{ + exception: { + type: 'Error', + value: 'lol' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com/override.js', + lineno: 10, + in_app: true + }] + }, + culprit: 'http://example.com/override.js', + message: 'lol at 10', + extra: 'awesome' + }]); + }); + + it('should ignored falsey messages', function() { + this.sinon.stub(window, 'send'); + + processException('Error', '', 'http://example.com', []); + assert.isFalse(window.send.called); + + processException('TypeError', '', 'http://example.com', []); + assert.isTrue(window.send.called); + }); + + it('should not blow up with `undefined` message', function() { + this.sinon.stub(window, 'send'); + + processException('TypeError', undefined, 'http://example.com', []); + assert.isTrue(window.send.called); + }); + + it('should truncate messages to the specified length', function() { + this.sinon.stub(window, 'send'); + + processException('TypeError', new Array(500).join('a'), 'http://example.com', []); + assert.deepEqual(window.send.lastCall.args, [{ + message: new Array(101).join('a')+'\u2026 at ', + exception: { + type: 'TypeError', + value: new Array(101).join('a')+'\u2026' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com', + lineno: [], + in_app: true + }] + }, + culprit: 'http://example.com', + }]); + + globalOptions.maxMessageLength = 150; + + processException('TypeError', new Array(500).join('a'), 'http://example.com', []); + assert.deepEqual(window.send.lastCall.args, [{ + message: new Array(151).join('a')+'\u2026 at ', + exception: { + type: 'TypeError', + value: new Array(151).join('a')+'\u2026' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com', + lineno: [], + in_app: true + }] + }, + culprit: 'http://example.com', + }]); + }); + }); + + describe('send', function() { + it('should check `isSetup`', function() { + this.sinon.stub(window, 'isSetup').returns(false); + this.sinon.stub(window, 'makeRequest'); + + send(); + assert.isTrue(window.isSetup.calledOnce); + assert.isFalse(window.makeRequest.calledOnce); + }); + + it('should build a good data payload', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalProject = '2'; + globalOptions = { + logger: 'javascript' + }; + + send({foo: 'bar'}); + assert.deepEqual(window.makeRequest.lastCall.args[0], { + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + foo: 'bar', + extra: {'session:duration': 100} + }); + }); + + it('should build a good data payload with a User', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalProject = '2'; + globalOptions = { + logger: 'javascript' + }; + + globalUser = {name: 'Matt'}; + + send({foo: 'bar'}); + assert.deepEqual(window.makeRequest.lastCall.args, [{ + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + user: { + name: 'Matt' + }, + foo: 'bar', + extra: {'session:duration': 100} + }]); + }); + + it('should merge in global tags', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalProject = '2'; + globalOptions = { + logger: 'javascript', + tags: {tag1: 'value1'} + }; + + + send({tags: {tag2: 'value2'}}); + assert.deepEqual(window.makeRequest.lastCall.args, [{ + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + tags: {tag1: 'value1', tag2: 'value2'}, + extra: {'session:duration': 100} + }]); + assert.deepEqual(globalOptions, { + logger: 'javascript', + tags: {tag1: 'value1'} + }); + }); + + it('should merge in global extra', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalProject = '2'; + globalOptions = { + logger: 'javascript', + extra: {key1: 'value1'} + }; + + + send({extra: {key2: 'value2'}}); + assert.deepEqual(window.makeRequest.lastCall.args, [{ + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + extra: {key1: 'value1', key2: 'value2', 'session:duration': 100} + }]); + assert.deepEqual(globalOptions, { + logger: 'javascript', + extra: {key1: 'value1'} + }); + }); + + it('should let dataCallback override everything', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + + globalOptions = { + projectId: 2, + logger: 'javascript', + dataCallback: function() { + return {lol: 'ibrokeit'}; + } + }; + + globalUser = {name: 'Matt'}; + + send({foo: 'bar'}); + assert.deepEqual(window.makeRequest.lastCall.args, [{ + lol: 'ibrokeit', + event_id: 'abc123', + }]); + }); + + it('should ignore dataCallback if it does not return anything', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalProject = '2'; + globalOptions = { + logger: 'javascript', + dataCallback: function() { + return; + } + }; + + send({foo: 'bar'}); + assert.deepEqual(window.makeRequest.lastCall.args[0], { + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + foo: 'bar', + extra: {'session:duration': 100} + }); + }); + + it('should strip empty tags', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalOptions = { + projectId: 2, + logger: 'javascript', + tags: {} + }; + + send({foo: 'bar', tags: {}, extra: {}}); + assert.deepEqual(window.makeRequest.lastCall.args[0], { + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + foo: 'bar', + extra: {'session:duration': 100} + }); + }); + + it('should attach release if available', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(window, 'getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + globalOptions = { + projectId: 2, + logger: 'javascript', + release: 'abc123', + }; + + send({foo: 'bar'}); + assert.deepEqual(window.makeRequest.lastCall.args[0], { + project: '2', + release: 'abc123', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + foo: 'bar', + extra: {'session:duration': 100} + }); + }); + }); + + describe('makeRequest', function() { + it('should load an Image', function() { + authQueryString = '?lol'; + globalServer = 'http://localhost/'; + var imageCache = []; + this.sinon.stub(window, 'newImage', function(){ var img = {}; imageCache.push(img); return img; }); + + makeRequest({foo: 'bar'}); + assert.equal(imageCache.length, 1); + assert.equal(imageCache[0].src, 'http://localhost/?lol&sentry_data=%7B%22foo%22%3A%22bar%22%7D'); + }); + }); + + describe('handleStackInfo', function() { + it('should work as advertised', function() { + var frame = {url: 'http://example.com'}; + this.sinon.stub(window, 'normalizeFrame').returns(frame); + this.sinon.stub(window, 'processException'); + + var stackInfo = { + name: 'Matt', + message: 'hey', + url: 'http://example.com', + lineno: 10, + stack: [ + frame, frame + ] + }; + + handleStackInfo(stackInfo, {foo: 'bar'}); + assert.deepEqual(window.processException.lastCall.args, [ + 'Matt', 'hey', 'http://example.com', 10, [frame, frame], {foo: 'bar'} + ]); + }); + + it('should work as advertised #integration', function() { + this.sinon.stub(window, 'makeRequest'); + var stackInfo = { + name: 'Error', + message: 'crap', + url: 'http://example.com', + lineno: 10, + stack: [ + { + url: 'http://example.com/file1.js', + line: 10, + column: 11, + func: 'broken', + context: [ + 'line1', + 'line2', + 'line3' + ] + }, + { + url: 'http://example.com/file2.js', + line: 12, + column: 13, + func: 'lol', + context: [ + 'line4', + 'line5', + 'line6' + ] + } + ] + }; + + handleStackInfo(stackInfo, {foo: 'bar'}); + assert.isTrue(window.makeRequest.calledOnce); + /* This is commented out because chai is broken. + + assert.deepEqual(window.makeRequest.lastCall.args, [{ + project: '2', + logger: 'javascript', + platform: 'javascript', + request: { + url: window.location.protocol + '//' + window.location.host + window.location.pathname, + querystring: window.location.search.slice(1) + }, + exception: { + type: 'Error', + value: 'crap' + }, + stacktrace: { + frames: [{ + filename: 'http://example.com/file1.js', + filename: 'file1.js', + lineno: 10, + colno: 11, + 'function': 'broken', + post_context: ['line3'], + context_line: 'line2', + pre_context: ['line1'] + }, { + filename: 'http://example.com/file2.js', + filename: 'file2.js', + lineno: 12, + colno: 13, + 'function': 'lol', + post_context: ['line6'], + context_line: 'line5', + pre_context: ['line4'] + }] + }, + culprit: 'http://example.com', + message: 'crap at 10', + foo: 'bar' + }]); + */ + }); + + it('should ignore frames that dont have a url', function() { + this.sinon.stub(window, 'normalizeFrame').returns(undefined); + this.sinon.stub(window, 'processException'); + + var stackInfo = { + name: 'Matt', + message: 'hey', + url: 'http://example.com', + lineno: 10, + stack: new Array(2) + }; + + handleStackInfo(stackInfo, {foo: 'bar'}); + assert.deepEqual(window.processException.lastCall.args, [ + 'Matt', 'hey', 'http://example.com', 10, [], {foo: 'bar'} + ]); + }); + + it('should not shit when there is no stack object from TK', function() { + this.sinon.stub(window, 'normalizeFrame').returns(undefined); + this.sinon.stub(window, 'processException'); + + var stackInfo = { + name: 'Matt', + message: 'hey', + url: 'http://example.com', + lineno: 10 + // stack: new Array(2) + }; + + handleStackInfo(stackInfo); + assert.isFalse(window.normalizeFrame.called); + assert.deepEqual(window.processException.lastCall.args, [ + 'Matt', 'hey', 'http://example.com', 10, [], undefined + ]); + }); + + it('should detect 2-words patterns (angularjs frequent case)', function() { + this.sinon.stub(window, 'normalizeFrame').returns(undefined); + this.sinon.stub(window, 'processException'); + + var stackInfo = { + name: 'new <anonymous>', + message: 'hey', + url: 'http://example.com', + lineno: 10 + // stack: new Array(2) + }; + + handleStackInfo(stackInfo); + assert.isFalse(window.normalizeFrame.called); + assert.deepEqual(window.processException.lastCall.args, [ + 'new <anonymous>', 'hey', 'http://example.com', 10, [], undefined + ]); + }); + }); + + describe('joinRegExp', function() { + it('should work as advertised', function() { + assert.equal(joinRegExp([ + 'a', 'b', 'a.b', /d/, /[0-9]/ + ]).source, 'a|b|a\\.b|d|[0-9]'); + }); + + it('should not process empty or undefined variables', function() { + assert.equal(joinRegExp([ + 'a', 'b', null, undefined + ]).source, 'a|b'); + }); + + it('should skip entries that are not strings or regular expressions in the passed array of patterns', function() { + assert.equal(joinRegExp([ + 'a', 'b', null, 'a.b', undefined, true, /d/, 123, {}, /[0-9]/, [] + ]).source, 'a|b|a\\.b|d|[0-9]'); + }); + }); +}); + +describe('Raven (public API)', function() { + afterEach(function() { + flushRavenState(); + }); + + describe('.VERSION', function() { + it('should have a version', function() { + assert.isString(Raven.VERSION); + }); + }); + + describe('ignore errors', function() { + it('should install default ignore errors', function() { + Raven.config('//abc@example.com/2'); + + assert.isTrue(globalOptions.ignoreErrors.test('Script error'), 'it should install "Script error" by default'); + assert.isTrue(globalOptions.ignoreErrors.test('Script error.'), 'it should install "Script error." by default'); + assert.isTrue(globalOptions.ignoreErrors.test('Javascript error: Script error on line 0'), 'it should install "Javascript error: Script error on line 0" by default'); + assert.isTrue(globalOptions.ignoreErrors.test('Javascript error: Script error. on line 0'), 'it should install "Javascript error: Script error. on line 0" by default'); + }); + }); + + describe('callback function', function() { + it('should callback a function if it is global', function() { + window.RavenConfig = { + dsn: "http://random@some.other.server:80/2", + config: {some: 'config'} + }; + + this.sinon.stub(window, 'isSetup').returns(false); + this.sinon.stub(TraceKit.report, 'subscribe'); + + afterLoad(); + + assert.equal(globalKey, 'random'); + assert.equal(globalServer, 'http://some.other.server:80/api/2/store/'); + + assert.equal(globalOptions.some, 'config'); + assert.equal(globalProject, '2'); + + assert.isTrue(window.isSetup.calledOnce); + assert.isFalse(TraceKit.report.subscribe.calledOnce); + + delete window.RavenConfig; + }); + }); + + describe('.config', function() { + it('should work with a DSN', function() { + assert.equal(Raven, Raven.config(SENTRY_DSN, {foo: 'bar'}), 'it should return Raven'); + assert.equal(globalKey, 'abc'); + assert.equal(globalServer, 'http://example.com:80/api/2/store/'); + assert.equal(globalOptions.foo, 'bar'); + assert.equal(globalProject, '2'); + assert.isTrue(isSetup()); + }); + + it('should work with a protocol relative DSN', function() { + Raven.config('//abc@example.com/2'); + assert.equal(globalKey, 'abc'); + assert.equal(globalServer, '//example.com/api/2/store/'); + assert.equal(globalProject, '2'); + assert.isTrue(isSetup()); + }); + + it('should work should work at a non root path', function() { + Raven.config('//abc@example.com/sentry/2'); + assert.equal(globalKey, 'abc'); + assert.equal(globalServer, '//example.com/sentry/api/2/store/'); + assert.equal(globalProject, '2'); + assert.isTrue(isSetup()); + }); + + it('should noop a falsey dsn', function() { + Raven.config(''); + assert.isFalse(isSetup()); + }); + + it('should return Raven for a falsey dsn', function() { + assert.equal(Raven.config(''), Raven); + }); + + it('should not set global options more than once', function() { + this.sinon.spy(window, 'parseDSN'); + this.sinon.stub(window, 'logDebug'); + setupRaven(); + setupRaven(); + assert.isTrue(parseDSN.calledOnce); + assert.isTrue(logDebug.called); + }); + + describe('whitelistUrls', function() { + it('should be false if none are passed', function() { + Raven.config('//abc@example.com/2'); + assert.equal(globalOptions.whitelistUrls, false); + }); + + it('should join into a single RegExp', function() { + Raven.config('//abc@example.com/2', { + whitelistUrls: [ + /my.app/i, + /other.app/i + ] + }); + + assert.match(globalOptions.whitelistUrls, /my.app|other.app/i); + }); + + it('should handle strings as well', function() { + Raven.config('//abc@example.com/2', { + whitelistUrls: [ + /my.app/i, + "stringy.app" + ] + }); + + assert.match(globalOptions.whitelistUrls, /my.app|stringy.app/i); + }); + }); + + describe('collectWindowErrors', function() { + it('should be true by default', function() { + Raven.config(SENTRY_DSN); + assert.isTrue(TraceKit.collectWindowErrors); + }); + + it('should be true if set to true', function() { + Raven.config(SENTRY_DSN, { + collectWindowErrors: true + }); + + assert.isTrue(TraceKit.collectWindowErrors); + }); + + it('should be false if set to false', function() { + Raven.config(SENTRY_DSN, { + collectWindowErrors: false + }); + + assert.isFalse(TraceKit.collectWindowErrors); + }); + }); + }); + + describe('.install', function() { + it('should check `isSetup`', function() { + this.sinon.stub(window, 'isSetup').returns(false); + this.sinon.stub(TraceKit.report, 'subscribe'); + Raven.install(); + assert.isTrue(window.isSetup.calledOnce); + assert.isFalse(TraceKit.report.subscribe.calledOnce); + }); + + it('should register itself with TraceKit', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(TraceKit.report, 'subscribe'); + assert.equal(Raven, Raven.install()); + assert.isTrue(TraceKit.report.subscribe.calledOnce); + assert.equal(TraceKit.report.subscribe.lastCall.args[0], handleStackInfo); + }); + + it('should not register itself more than once', function() { + this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(TraceKit.report, 'subscribe'); + Raven.install(); + Raven.install(); + assert.isTrue(TraceKit.report.subscribe.calledOnce); + }); + }); + + describe('.wrap', function() { + it('should return a wrapped callback', function() { + var spy = this.sinon.spy(); + var wrapped = Raven.wrap(spy); + assert.isFunction(wrapped); + assert.isTrue(wrapped.__raven__); + wrapped(); + assert.isTrue(spy.calledOnce); + }); + + it('should copy property when wrapping function', function() { + var func = function() {}; + func.test = true; + var wrapped = Raven.wrap(func); + assert.isTrue(wrapped.test); + }); + + it('should not copy prototype property when wrapping function', function() { + var func = function() {}; + func.prototype.test = true; + var wrapped = Raven.wrap(func); + assert.isUndefined(new wrapped().test); + }); + + it('should return the result of a wrapped function', function() { + var func = function() { return 'foo'; }; + var wrapped = Raven.wrap(func); + assert.equal(wrapped(), 'foo'); + }); + + it('should not wrap a non-function', function() { + assert.equal(Raven.wrap('lol'), 'lol'); + assert.equal(Raven.wrap({}, 'lol'), 'lol'); + assert.equal(Raven.wrap(undefined, 'lol'), 'lol'); + var a = [1, 2]; + assert.equal(Raven.wrap(a), a); + }); + + it('should wrap function arguments', function() { + var spy = this.sinon.spy(); + var wrapped = Raven.wrap(function(f) { + assert.isTrue(f.__raven__); + f(); + }); + wrapped(spy); + assert.isTrue(spy.calledOnce); + }); + + it('should not wrap function arguments', function() { + var spy = this.sinon.spy(); + var wrapped = Raven.wrap({ deep: false }, function(f) { + assert.isUndefined(f.__raven__); + f(); + }); + wrapped(spy); + assert.isTrue(spy.calledOnce); + }); + + it('should maintain the correct scope', function() { + var foo = {}; + var bar = function() { + assert.equal(this, foo); + }; + bar.apply(foo, []); + Raven.wrap(bar).apply(foo, []); + }); + + it('should re-raise a thrown exception', function() { + var error = new Error('lol'); + assert.throws(function() { + Raven.wrap(function() { throw error; })(); + }, error); + }); + + }); + + describe('.context', function() { + it('should execute the callback with options', function() { + var spy = this.sinon.spy(); + this.sinon.stub(Raven, 'captureException'); + Raven.context({'foo': 'bar'}, spy); + assert.isTrue(spy.calledOnce); + assert.isFalse(Raven.captureException.called); + }); + + it('should execute the callback with arguments', function() { + var spy = this.sinon.spy(); + var args = [1, 2]; + Raven.context(spy, args); + assert.deepEqual(spy.lastCall.args, args); + }); + + it('should execute the callback without options', function() { + var spy = this.sinon.spy(); + this.sinon.stub(Raven, 'captureException'); + Raven.context(spy); + assert.isTrue(spy.calledOnce); + assert.isFalse(Raven.captureException.called); + }); + + it('should capture the exception with options', function() { + var error = new Error('crap'); + var broken = function() { throw error; }; + this.sinon.stub(Raven, 'captureException'); + assert.throws(function() { + Raven.context({foo: 'bar'}, broken); + }, error); + assert.isTrue(Raven.captureException.called); + assert.deepEqual(Raven.captureException.lastCall.args, [error, {'foo': 'bar'}]); + }); + + it('should capture the exception without options', function() { + var error = new Error('crap'); + var broken = function() { throw error; }; + this.sinon.stub(Raven, 'captureException'); + assert.throws(function() { + Raven.context(broken); + }, error); + assert.isTrue(Raven.captureException.called); + assert.deepEqual(Raven.captureException.lastCall.args, [error, undefined]); + }); + + it('should execute the callback without arguments', function() { + // This is only reproducable in a browser that complains about passing + // undefined to Function.apply + var spy = this.sinon.spy(); + Raven.context(spy); + assert.deepEqual(spy.lastCall.args, []); + }); + + it('should return the result of the wrapped function', function() { + var val = {}; + var func = function() { return val; }; + assert.equal(Raven.context(func), val); + }); + }); + + describe('.uninstall', function() { + it('should uninstall from TraceKit', function() { + this.sinon.stub(TraceKit.report, 'uninstall'); + Raven.uninstall(); + assert.isTrue(TraceKit.report.uninstall.calledOnce); + }); + + it('should set isRavenInstalled flag to false', function() { + isRavenInstalled = true; + this.sinon.stub(TraceKit.report, 'uninstall'); + Raven.uninstall(); + assert.isFalse(isRavenInstalled); + }); + }); + + describe('.setUserContext', function() { + it('should set the globalUser object', function() { + Raven.setUserContext({name: 'Matt'}); + assert.deepEqual(globalUser, {name: 'Matt'}); + }); + + it('should clear the globalUser with no arguments', function() { + globalUser = {name: 'Matt'}; + Raven.setUserContext(); + assert.isUndefined(globalUser); + }); + }); + + describe('.setExtraContext', function() { + it('should set the globalOptions.extra object', function() { + Raven.setExtraContext({name: 'Matt'}); + assert.deepEqual(globalOptions.extra, {name: 'Matt'}); + }); + + it('should clear globalOptions.extra with no arguments', function() { + globalOptions = {name: 'Matt'}; + Raven.setExtraContext(); + assert.deepEqual(globalOptions.extra, {}); + }); + }); + + describe('.setTagsContext', function() { + it('should set the globalOptions.tags object', function() { + Raven.setTagsContext({name: 'Matt'}); + assert.deepEqual(globalOptions.tags, {name: 'Matt'}); + }); + + it('should clear globalOptions.tags with no arguments', function() { + globalOptions = {name: 'Matt'}; + Raven.setTagsContext(); + assert.deepEqual(globalOptions.tags, {}); + }); + }); + + describe('.setReleaseContext', function() { + it('should set the globalOptions.release attribute', function() { + Raven.setReleaseContext('abc123'); + assert.equal(globalOptions.release, 'abc123'); + }); + + it('should clear globalOptions.release with no arguments', function() { + globalOptions.release = 'abc123'; + Raven.setReleaseContext(); + assert.isUndefined(globalOptions.release); + }); + }); + + describe('.setDataCallback', function() { + it('should set the globalOptions.dataCallback attribute', function() { + var foo = function(){}; + Raven.setDataCallback(foo); + assert.equal(globalOptions.dataCallback, foo); + }); + + it('should clear globalOptions.dataCallback with no arguments', function() { + var foo = function(){}; + globalOptions.dataCallback = foo; + Raven.setDataCallback(); + assert.isUndefined(globalOptions.dataCallback); + }); + }); + + describe('.setShouldSendCallback', function() { + it('should set the globalOptions.shouldSendCallback attribute', function() { + var foo = function(){}; + Raven.setShouldSendCallback(foo); + assert.equal(globalOptions.shouldSendCallback, foo); + }); + + it('should clear globalOptions.shouldSendCallback with no arguments', function() { + var foo = function(){}; + globalOptions.shouldSendCallback = foo; + Raven.setShouldSendCallback(); + assert.isUndefined(globalOptions.shouldSendCallback); + }); + }); + + describe('.captureMessage', function() { + it('should work as advertised', function() { + this.sinon.stub(window, 'send'); + Raven.captureMessage('lol', {foo: 'bar'}); + assert.deepEqual(window.send.lastCall.args, [{ + message: 'lol', + foo: 'bar' + }]); + }); + + it('should coerce message to a string', function() { + this.sinon.stub(window, 'send'); + Raven.captureMessage({}); + assert.deepEqual(window.send.lastCall.args, [{ + message: '[object Object]' + }]); + }); + + it('should work as advertised #integration', function() { + var imageCache = []; + this.sinon.stub(window, 'newImage', function(){ var img = {}; imageCache.push(img); return img; }); + + setupRaven(); + Raven.captureMessage('lol', {foo: 'bar'}); + assert.equal(imageCache.length, 1); + // It'd be hard to assert the actual payload being sent + // since it includes the generated url, which is going to + // vary between users running the tests + // Unit tests should cover that the payload was constructed properly + }); + + it('should tag lastEventId #integration', function() { + setupRaven(); + Raven.captureMessage('lol'); + assert.equal(Raven.lastEventId(), 'abc123'); + }); + + it('should respect `ignoreErrors`', function() { + this.sinon.stub(window, 'send'); + + globalOptions.ignoreErrors = joinRegExp(['e1', 'e2']); + Raven.captureMessage('e1'); + assert.isFalse(window.send.called); + Raven.captureMessage('e2'); + assert.isFalse(window.send.called); + Raven.captureMessage('Non-ignored error'); + assert.isTrue(window.send.calledOnce); + }); + }); + + describe('.captureException', function() { + it('should call TraceKit.report', function() { + var error = new Error('crap'); + this.sinon.stub(TraceKit, 'report'); + Raven.captureException(error, {foo: 'bar'}); + assert.isTrue(TraceKit.report.calledOnce); + assert.deepEqual(TraceKit.report.lastCall.args, [error, {foo: 'bar'}]); + }); + + it('should store the last exception', function() { + var error = new Error('crap'); + this.sinon.stub(TraceKit, 'report'); + Raven.captureException(error); + assert.equal(Raven.lastException(), error); + }); + + it('shouldn\'t reraise the if the error is the same error', function() { + var error = new Error('crap'); + this.sinon.stub(TraceKit, 'report').throws(error); + // this would raise if the errors didn't match + Raven.captureException(error, {foo: 'bar'}); + assert.isTrue(TraceKit.report.calledOnce); + }); + + it('should reraise a different error', function() { + var error = new Error('crap1'); + this.sinon.stub(TraceKit, 'report').throws(error); + assert.throws(function() { + Raven.captureException(new Error('crap2')); + }, error); + }); + + it('should capture as a normal message if a non-Error is passed', function() { + this.sinon.stub(Raven, 'captureMessage'); + this.sinon.stub(TraceKit, 'report'); + Raven.captureException('derp'); + assert.equal(Raven.captureMessage.lastCall.args[0], 'derp'); + assert.isFalse(TraceKit.report.called); + Raven.captureException(true); + assert.equal(Raven.captureMessage.lastCall.args[0], true); + assert.isFalse(TraceKit.report.called); + }); + }); + + describe('.isSetup', function() { + it('should work as advertised', function() { + var isSetup = this.sinon.stub(window, 'isSetup'); + isSetup.returns(true); + assert.isTrue(Raven.isSetup()); + isSetup.returns(false); + assert.isFalse(Raven.isSetup()); + }); + }); +}); diff --git a/htdocs/includes/raven-js/vendor/TraceKit/tracekit.js b/htdocs/includes/raven-js/vendor/TraceKit/tracekit.js new file mode 100644 index 0000000000000000000000000000000000000000..bccddabedbe4ed46411ecc2122cf932993d46d1c --- /dev/null +++ b/htdocs/includes/raven-js/vendor/TraceKit/tracekit.js @@ -0,0 +1,1044 @@ +/* + TraceKit - Cross brower stack traces - github.com/occ/TraceKit + MIT license +*/ + +var TraceKit = { + remoteFetching: false, + collectWindowErrors: true, + // 3 lines before, the offending line, 3 lines after + linesOfContext: 7 +}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + + +/** + * TraceKit.wrap: Wrap any function in a TraceKit reporter + * Example: func = TraceKit.wrap(func); + * + * @param {Function} func Function to be wrapped + * @return {Function} The wrapped func + */ +TraceKit.wrap = function traceKitWrapper(func) { + function wrapped() { + try { + return func.apply(this, arguments); + } catch (e) { + TraceKit.report(e); + throw e; + } + } + return wrapped; +}; + +/** + * TraceKit.report: cross-browser processing of unhandled exceptions + * + * Syntax: + * TraceKit.report.subscribe(function(stackInfo) { ... }) + * TraceKit.report.unsubscribe(function(stackInfo) { ... }) + * TraceKit.report(exception) + * try { ...code... } catch(ex) { TraceKit.report(ex); } + * + * Supports: + * - Firefox: full stack trace with line numbers, plus column number + * on top frame; column number is not guaranteed + * - Opera: full stack trace with line and column numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * - IE: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * + * In theory, TraceKit should work on all of the following versions: + * - IE5.5+ (only 8.0 tested) + * - Firefox 0.9+ (only 3.5+ tested) + * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require + * Exceptions Have Stacktrace to be enabled in opera:config) + * - Safari 3+ (only 4+ tested) + * - Chrome 1+ (only 5+ tested) + * - Konqueror 3.5+ (untested) + * + * Requires TraceKit.computeStackTrace. + * + * Tries to catch all unhandled exceptions and report them to the + * subscribed handlers. Please note that TraceKit.report will rethrow the + * exception. This is REQUIRED in order to get a useful stack trace in IE. + * If the exception does not reach the top of the browser, you will only + * get a stack trace from the point where TraceKit.report was called. + * + * Handlers receive a stackInfo object as described in the + * TraceKit.computeStackTrace docs. + */ +TraceKit.report = (function reportModuleWrapper() { + var handlers = [], + lastArgs = null, + lastException = null, + lastExceptionStack = null; + + /** + * Add a crash handler. + * @param {Function} handler + */ + function subscribe(handler) { + installGlobalHandler(); + handlers.push(handler); + } + + /** + * Remove a crash handler. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.<string, *>} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (hasKey(handlers, i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + var _oldOnerrorHandler, _onErrorHandlerInstalled; + + /** + * Ensures all global unhandled exceptions are recorded. + * Supported by Gecko and IE. + * @param {string} message Error message. + * @param {string} url URL of script that generated the exception. + * @param {(number|string)} lineNo The line number at which the error + * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. + */ + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); + processLastException(); + } else if (ex) { + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(ex); + notifyHandlers(stack, true); + } else { + var location = { + 'url': url, + 'line': lineNo, + 'column': colNo + }; + location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); + location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); + stack = { + 'message': message, + 'url': document.location.href, + 'stack': [location] + }; + notifyHandlers(stack, true); + } + + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } + + return false; + } + + function installGlobalHandler () + { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = window.onerror; + window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler () + { + if (!_onErrorHandlerInstalled) { + return; + } + window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + + /** + * Reports an unhandled Error to TraceKit. + * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. + */ + function report(ex, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + processLastException(); + } + } + + var stack = TraceKit.computeStackTrace(ex); + lastExceptionStack = stack; + lastException = ex; + lastArgs = args; + + // If the stack trace is incomplete, wait for 2 seconds for + // slow slow IE to see if onerror occurs or not before reporting + // this exception; otherwise, we will end up with an incomplete + // stack trace + window.setTimeout(function () { + if (lastException === ex) { + processLastException(); + } + }, (stack.incomplete ? 2000 : 0)); + + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } + } + + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; +}()); + +/** + * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript + * + * Syntax: + * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) + * Returns: + * s.name - exception name + * s.message - exception message + * s.stack[i].url - JavaScript or HTML file URL + * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) + * s.stack[i].args - arguments passed to the function, if known + * s.stack[i].line - line number, if known + * s.stack[i].column - column number, if known + * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# + * + * Supports: + * - Firefox: full stack trace with line numbers and unreliable column + * number on top frame + * - Opera 10: full stack trace with line and column numbers + * - Opera 9-: full stack trace with line numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the topmost stacktrace element + * only + * - IE: no line numbers whatsoever + * + * Tries to guess names of anonymous functions by looking for assignments + * in the source code. In IE and Safari, we have to guess source file names + * by searching for function bodies inside all page scripts. This will not + * work for scripts that are loaded cross-domain. + * Here be dragons: some function names may be guessed incorrectly, and + * duplicate functions may be mismatched. + * + * TraceKit.computeStackTrace should only be used for tracing purposes. + * Logging of unhandled exceptions should be done with TraceKit.report, + * which builds on top of TraceKit.computeStackTrace and provides better + * IE support by utilizing the window.onerror event to retrieve information + * about the top of the stack. + * + * Note: In IE and Safari, no stack trace is recorded on the Error object, + * so computeStackTrace instead walks its *own* chain of callers. + * This means that: + * * in Safari, some methods may be missing from the stack trace; + * * in IE, the topmost function in the stack trace will always be the + * caller of computeStackTrace. + * + * This is okay for tracing (because you are likely to be calling + * computeStackTrace from the function you want to be the topmost element + * of the stack trace anyway), but not okay for logging unhandled + * exceptions (because your catch block will likely be far away from the + * inner function that actually caused the exception). + * + */ +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + var debug = false, + sourceCache = {}; + + /** + * Attempts to retrieve source code via XMLHttpRequest, which is used + * to look up anonymous function names. + * @param {string} url URL of source code. + * @return {string} Source contents. + */ + function loadSource(url) { + if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. + return ''; + } + try { + var getXHR = function() { + try { + return new window.XMLHttpRequest(); + } catch (e) { + // explicitly bubble up the exception if not found + return new window.ActiveXObject('Microsoft.XMLHTTP'); + } + }; + + var request = getXHR(); + request.open('GET', url, false); + request.send(''); + return request.responseText; + } catch (e) { + return ''; + } + } + + /** + * Retrieves source code from the source code cache. + * @param {string} url URL of source code. + * @return {Array.<string>} Source contents. + */ + function getSource(url) { + if (!isString(url)) return []; + if (!hasKey(sourceCache, url)) { + // URL needs to be able to fetched within the acceptable domain. Otherwise, + // cross-domain errors will be triggered. + var source = ''; + if (url.indexOf(document.domain) !== -1) { + source = loadSource(url); + } + sourceCache[url] = source ? source.split('\n') : []; + } + + return sourceCache[url]; + } + + /** + * Tries to use an externally loaded copy of source code to determine + * the name of a function by looking at the name of the variable it was + * assigned to, if any. + * @param {string} url URL of source code. + * @param {(string|number)} lineNo Line number in source code. + * @return {string} The function name, if discoverable. + */ + function guessFunctionName(url, lineNo) { + var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, + reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, + line = '', + maxLines = 10, + source = getSource(url), + m; + + if (!source.length) { + return UNKNOWN_FUNCTION; + } + + // Walk backwards from the first line in the function until we find the line which + // matches the pattern above, which is the function definition + for (var i = 0; i < maxLines; ++i) { + line = source[lineNo - i] + line; + + if (!isUndefined(line)) { + if ((m = reGuessFunction.exec(line))) { + return m[1]; + } else if ((m = reFunctionArgNames.exec(line))) { + return m[1]; + } + } + } + + return UNKNOWN_FUNCTION; + } + + /** + * Retrieves the surrounding lines from where an exception occurred. + * @param {string} url URL of source code. + * @param {(string|number)} line Line number in source code to centre + * around for context. + * @return {?Array.<string>} Lines of source code. + */ + function gatherContext(url, line) { + var source = getSource(url); + + if (!source.length) { + return null; + } + + var context = [], + // linesBefore & linesAfter are inclusive with the offending line. + // if linesOfContext is even, there will be one extra line + // *before* the offending line. + linesBefore = Math.floor(TraceKit.linesOfContext / 2), + // Add one extra line if linesOfContext is odd + linesAfter = linesBefore + (TraceKit.linesOfContext % 2), + start = Math.max(0, line - linesBefore - 1), + end = Math.min(source.length, line + linesAfter - 1); + + line -= 1; // convert to 0-based index + + for (var i = start; i < end; ++i) { + if (!isUndefined(source[i])) { + context.push(source[i]); + } + } + + return context.length > 0 ? context : null; + } + + /** + * Escapes special characters, except for whitespace, in a string to be + * used inside a regular expression as a string literal. + * @param {string} text The string. + * @return {string} The escaped string literal. + */ + function escapeRegExp(text) { + return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); + } + + /** + * Escapes special characters in a string to be used inside a regular + * expression as a string literal. Also ensures that HTML entities will + * be matched the same as their literal friends. + * @param {string} body The string. + * @return {string} The escaped string. + */ + function escapeCodeAsRegExpForMatchingInsideHTML(body) { + return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); + } + + /** + * Determines where a code fragment occurs in the source code. + * @param {RegExp} re The function definition. + * @param {Array.<string>} urls A list of URLs to search. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceInUrls(re, urls) { + var source, m; + for (var i = 0, j = urls.length; i < j; ++i) { + // console.log('searching', urls[i]); + if ((source = getSource(urls[i])).length) { + source = source.join('\n'); + if ((m = re.exec(source))) { + // console.log('Found function in ' + urls[i]); + + return { + 'url': urls[i], + 'line': source.substring(0, m.index).split('\n').length, + 'column': m.index - source.lastIndexOf('\n', m.index) - 1 + }; + } + } + } + + // console.log('no match'); + + return null; + } + + /** + * Determines at which column a code fragment occurs on a line of the + * source code. + * @param {string} fragment The code fragment. + * @param {string} url The URL to search. + * @param {(string|number)} line The line number to examine. + * @return {?number} The column number. + */ + function findSourceInLine(fragment, url, line) { + var source = getSource(url), + re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), + m; + + line -= 1; + + if (source && source.length > line && (m = re.exec(source[line]))) { + return m.index; + } + + return null; + } + + /** + * Determines where a function was defined within the source code. + * @param {(Function|string)} func A function reference or serialized + * function definition. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceByFunctionBody(func) { + var urls = [window.location.href], + scripts = document.getElementsByTagName('script'), + body, + code = '' + func, + codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + re, + parts, + result; + + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.src) { + urls.push(script.src); + } + } + + if (!(parts = codeRE.exec(code))) { + re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); + } + + // not sure if this is really necessary, but I don’t have a test + // corpus large enough to confirm that and it was in the original. + else { + var name = parts[1] ? '\\s+' + parts[1] : '', + args = parts[2].split(',').join('\\s*,\\s*'); + + body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); + re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); + } + + // look for a normal function definition + if ((result = findSourceInUrls(re, urls))) { + return result; + } + + // look for an old-school event handler function + if ((parts = eventRE.exec(code))) { + var event = parts[1]; + body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); + + // look for a function defined in HTML as an onXXX handler + re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); + + if ((result = findSourceInUrls(re, urls[0]))) { + return result; + } + + // look for ??? + re = new RegExp(body); + + if ((result = findSourceInUrls(re, urls))) { + return result; + } + } + + return null; + } + + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // FIREFOX: + // ex.message = qq is not defined + // ex.fileName = http://... + // ex.lineNumber = 59 + // ex.columnNumber = 69 + // ex.stack = ...stack trace... (see the example below) + // ex.name = ReferenceError + // + // CHROME: + // ex.message = qq is not defined + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (!ex.stack) { + return null; + } + + var chrome = /^\s*at (.*?) ?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(.*?)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i, + lines = ex.stack.split('\n'), + stack = [], + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = gecko.exec(lines[i]))) { + element = { + 'url': parts[3], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': parts[2] ? parts[2].split(',') : '', + 'line': +parts[4], + 'column': parts[5] ? +parts[5] : null + }; + } else if ((parts = chrome.exec(lines[i]))) { + element = { + 'url': parts[2], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'line': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + + if (element.line) { + element.context = gatherContext(element.url, element.line); + } + + stack.push(element); + } + + if (!stack.length) { + return null; + } + + if (stack[0].line && !stack[0].column && reference) { + stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); + } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + stack[0].column = ex.columnNumber + 1; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Computes stack trace information from the stacktrace property. + * Opera 10 uses this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStacktraceProp(ex) { + // Access and store the stacktrace property before doing ANYTHING + // else to it because Opera is not very good at providing it + // reliably in other circumstances. + var stacktrace = ex.stacktrace; + + var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i, + lines = stacktrace.split('\n'), + stack = [], + parts; + + for (var i = 0, j = lines.length; i < j; i += 2) { + if ((parts = testRE.exec(lines[i]))) { + var element = { + 'line': +parts[1], + 'column': +parts[2], + 'func': parts[3] || parts[4], + 'args': parts[5] ? parts[5].split(',') : [], + 'url': parts[6] + }; + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + if (element.line) { + try { + element.context = gatherContext(element.url, element.line); + } catch (exc) {} + } + + if (!element.context) { + element.context = [lines[i + 1]]; + } + + stack.push(element); + } + } + + if (!stack.length) { + return null; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * NOT TESTED. + * Computes stack trace information from an error message that includes + * the stack trace. + * Opera 9 and earlier use this method if the option to show stack + * traces is turned on in opera:config. + * @param {Error} ex + * @return {?Object.<string, *>} Stack information. + */ + function computeStackTraceFromOperaMultiLineMessage(ex) { + // Opera includes a stack trace into the exception message. An example is: + // + // Statement on line 3: Undefined variable: undefinedFunc + // Backtrace: + // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz + // undefinedFunc(a); + // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy + // zzz(x, y, z); + // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx + // yyy(a, a, a); + // Line 1 of function script + // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } + // ... + + var lines = ex.message.split('\n'); + if (lines.length < 4) { + return null; + } + + var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, + lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, + lineRE3 = /^\s*Line (\d+) of function script\s*$/i, + stack = [], + scripts = document.getElementsByTagName('script'), + inlineScriptBlocks = [], + parts, + i, + len, + source; + + for (i in scripts) { + if (hasKey(scripts, i) && !scripts[i].src) { + inlineScriptBlocks.push(scripts[i]); + } + } + + for (i = 2, len = lines.length; i < len; i += 2) { + var item = null; + if ((parts = lineRE1.exec(lines[i]))) { + item = { + 'url': parts[2], + 'func': parts[3], + 'line': +parts[1] + }; + } else if ((parts = lineRE2.exec(lines[i]))) { + item = { + 'url': parts[3], + 'func': parts[4] + }; + var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block + var script = inlineScriptBlocks[parts[2] - 1]; + if (script) { + source = getSource(item.url); + if (source) { + source = source.join('\n'); + var pos = source.indexOf(script.innerText); + if (pos >= 0) { + item.line = relativeLine + source.substring(0, pos).split('\n').length; + } + } + } + } else if ((parts = lineRE3.exec(lines[i]))) { + var url = window.location.href.replace(/#.*$/, ''), + line = parts[1]; + var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1])); + source = findSourceInUrls(re, [url]); + item = { + 'url': url, + 'line': source ? source.line : line, + 'func': '' + }; + } + + if (item) { + if (!item.func) { + item.func = guessFunctionName(item.url, item.line); + } + var context = gatherContext(item.url, item.line); + var midline = (context ? context[Math.floor(context.length / 2)] : null); + if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) { + item.context = context; + } else { + // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url); + item.context = [lines[i + 1]]; + } + stack.push(item); + } + } + if (!stack.length) { + return null; // could not parse multiline exception message as Opera stack trace + } + + return { + 'name': ex.name, + 'message': lines[0], + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Adds information about the first frame to incomplete stack traces. + * Safari and IE require this to get complete data on the first frame. + * @param {Object.<string, *>} stackInfo Stack trace information from + * one of the compute* methods. + * @param {string} url The URL of the script that caused an error. + * @param {(number|string)} lineNo The line number of the script that + * caused an error. + * @param {string=} message The error generated by the browser, which + * hopefully contains the name of the object that caused the error. + * @return {boolean} Whether or not the stack information was + * augmented. + */ + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { + var initial = { + 'url': url, + 'line': lineNo + }; + + if (initial.url && initial.line) { + stackInfo.incomplete = false; + + if (!initial.func) { + initial.func = guessFunctionName(initial.url, initial.line); + } + + if (!initial.context) { + initial.context = gatherContext(initial.url, initial.line); + } + + var reference = / '([^']+)' /.exec(message); + if (reference) { + initial.column = findSourceInLine(reference[1], initial.url, initial.line); + } + + if (stackInfo.stack.length > 0) { + if (stackInfo.stack[0].url === initial.url) { + if (stackInfo.stack[0].line === initial.line) { + return false; // already in stack trace + } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) { + stackInfo.stack[0].line = initial.line; + stackInfo.stack[0].context = initial.context; + return false; + } + } + } + + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } + + return false; + } + + /** + * Computes stack trace information by walking the arguments.caller + * chain at the time the exception occurred. This will cause earlier + * frames to be missed but is the only way to get any stack trace in + * Safari and IE. The top frame is restored by + * {@link augmentStackTraceWithInitialElement}. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceByWalkingCallerChain(ex, depth) { + var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, + stack = [], + funcs = {}, + recursion = false, + parts, + item, + source; + + for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) { + if (curr === computeStackTrace || curr === TraceKit.report) { + // console.log('skipping internal function'); + continue; + } + + item = { + 'url': null, + 'func': UNKNOWN_FUNCTION, + 'line': null, + 'column': null + }; + + if (curr.name) { + item.func = curr.name; + } else if ((parts = functionName.exec(curr.toString()))) { + item.func = parts[1]; + } + + if ((source = findSourceByFunctionBody(curr))) { + item.url = source.url; + item.line = source.line; + + if (item.func === UNKNOWN_FUNCTION) { + item.func = guessFunctionName(item.url, item.line); + } + + var reference = / '([^']+)' /.exec(ex.message || ex.description); + if (reference) { + item.column = findSourceInLine(reference[1], source.url, source.line); + } + } + + if (funcs['' + curr]) { + recursion = true; + }else{ + funcs['' + curr] = true; + } + + stack.push(item); + } + + if (depth) { + // console.log('depth is ' + depth); + // console.log('stack is ' + stack.length); + stack.splice(0, depth); + } + + var result = { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); + return result; + } + + /** + * Computes a stack trace for an exception. + * @param {Error} ex + * @param {(string|number)=} depth + */ + function computeStackTrace(ex, depth) { + var stack = null; + depth = (depth == null ? 0 : +depth); + + try { + // This must be tried first because Opera 10 *destroys* + // its stacktrace property if you try to access the stack + // property first!! + stack = computeStackTraceFromStacktraceProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromOperaMultiLineMessage(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + return {}; + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; + computeStackTrace.guessFunctionName = guessFunctionName; + computeStackTrace.gatherContext = gatherContext; + + return computeStackTrace; +}()); diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 6e822ab5252ee98c344597322cf33300a24f1174..d4f27fb3145dc9081303314a9b3dd0039912515f 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -1244,6 +1244,16 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs print '<script type="text/javascript" src="'.$pathckeditor.$jsckeditor.($ext?'?'.$ext:'').'"></script>'."\n"; } + // Raven.js for client-side Sentry logging support + if (array_key_exists('mod_syslog_sentry', $conf->loghandlers)) { + print '<!-- Includes Raven.js for Sentry -->' . "\n"; + print '<script src="' . DOL_URL_ROOT . '/includes/raven-js/dist/raven.min.js"></script>' . "\n"; + print '<script src="' . DOL_URL_ROOT . '/includes/raven-js/plugins/native.js"></script>' . "\n"; + if (! defined('DISABLE_JQUERY')) { + print '<script src="' . DOL_URL_ROOT . '/includes/raven-js/plugins/jquery.js"></script>' . "\n"; + } + } + // Global js function print '<!-- Includes JS of Dolibarr -->'."\n"; print '<script type="text/javascript" src="'.DOL_URL_ROOT.'/core/js/lib_head.js'.($ext?'?'.$ext:'').'"></script>'."\n"; @@ -1321,7 +1331,7 @@ function top_menu($head, $title='', $target='', $disablejs=0, $disablehead=0, $a // For backward compatibility with old modules if (empty($conf->headerdone)) top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss); - print '<body id="mainbody">'; + print '<body id="mainbody">' . "\n"; if ($conf->use_javascript_ajax) { @@ -1376,15 +1386,29 @@ function top_menu($head, $title='', $target='', $disablejs=0, $disablehead=0, $a paneSelector: "#mainContent" } } - </script>'; - } + </script>' . "\n"; + } - // Wrapper to show tooltips - print "\n".'<script type="text/javascript"> - jQuery(document).ready(function () { - jQuery(".classfortooltip").tipTip({maxWidth: "'.dol_size(600,'width').'px", edgeOffset: 10, delay: 50, fadeIn: 50, fadeOut: 50}); - }); - </script>'; + // Wrapper to show tooltips + print '<script type="text/javascript"> + jQuery(document).ready(function () { + jQuery(".classfortooltip").tipTip({maxWidth: "'.dol_size(600,'width').'px", edgeOffset: 10, delay: 50, fadeIn: 50, fadeOut: 50}); + }); +</script>' . "\n"; + + // Raven.js for client-side Sentry logging support + if (array_key_exists('mod_syslog_sentry', $conf->loghandlers) && ! empty($conf->global->SYSLOG_SENTRY_DSN)) { + + // Filter out secret key + $dsn = parse_url($conf->global->SYSLOG_SENTRY_DSN); + $public_dsn = $dsn['scheme'] . '://' . $dsn['user'] .'@' . $dsn['host'] . $dsn['path']; + + print '<script type="text/javascript">' . "\n"; + print "Raven.config('" . $public_dsn . "').install()\n"; + print "Raven.setUserContext({username: '" . $user->login . "'})\n"; + print "Raven.setTagsContext({version: '" . DOL_VERSION . "'})\n"; + print "</script>\n"; + } } /*