diff --git a/src/raven.js b/src/raven.js index 0e98d10e2c08..c44389c5230c 100644 --- a/src/raven.js +++ b/src/raven.js @@ -1,580 +1,633 @@ -'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 = !!(window.JSON && window.JSON.stringify), - globalServer, - globalUser, - globalKey, - globalProject, - globalOptions = { - logger: 'javascript', - ignoreErrors: [], - ignoreUrls: [], - whitelistUrls: [], - includePaths: [], - tags: {} - }; - -var TK = TraceKit.noConflict(); +;(function(global) { + 'use strict'; -// Disable Tracekit's remote fetching by default -TK.remoteFetching = false; + var _Raven = global.Raven, + TK = TraceKit.noConflict(); -/* - * The core Raven singleton - * - * @this {Raven} - */ -var Raven = { - VERSION: '@VERSION', + // Disable Tracekit's remote fetching by default + TK.remoteFetching = false; /* - * Allow Raven to be configured as soon as it is loaded - * It uses a global RavenConfig = {dsn: '...', config: {}} + * The core Raven singleton * - * @return undefined + * @this {Raven} */ - afterLoad: function() { - var globalConfig = window.RavenConfig; - if (globalConfig) { - this.config(globalConfig.dsn, globalConfig.config).install(); - } - }, + var Raven = { + VERSION: '@VERSION', + + // If there is no JSON, we no-op the core features of Raven + // since JSON is required to encode the payload + hasJSON: !!(global.JSON && global.JSON.stringify), + + server: null, + user: null, + key: null, + + options: { + logger: 'javascript', + ignoreErrors: [], + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + tags: {} + }, + + /* + * Allow Raven to be configured as soon as it is loaded + * It uses a global RavenConfig = {dsn: '...', config: {}} + * + * @return undefined + */ + afterLoad: function() { + if (global.RavenConfig) { + this.config(global.RavenConfig.dsn, global.RavenConfig.config).install(); + } + }, + + /* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ + noConflict: function() { + global.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) { + var uri = this.parseUri(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); + + // merge in options + if (options) { + this.each(options, this.hitch(this, function(key, value) { + this.options[key] = value; + })); + } - /* - * 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; - }, + // join regexp rules into one big rule + this.options.ignoreUrls = this.options.ignoreUrls.length ? this.joinRegExp(this.options.ignoreUrls) : false; + this.options.whitelistUrls = this.options.whitelistUrls.length ? this.joinRegExp(this.options.whitelistUrls) : false; + this.options.includePaths = this.joinRegExp(this.options.includePaths); - /* - * 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) { - var uri = parseUri(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. + this.options.ignoreErrors.push('Script error.'); - // join regexp rules into one big rule - globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false; - globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false; - globalOptions.includePaths = joinRegExp(globalOptions.includePaths); + this.key = uri.user; + this.project = ~~uri.path.substr(lastSlash + 1); - // "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.'); + // assemble the endpoint from the uri pieces + this.server = '//' + uri.host + + (uri.port ? ':' + uri.port : '') + + '/' + path + 'api/' + this.project + '/store/'; - globalKey = uri.user; - globalProject = ~~uri.path.substr(lastSlash + 1); + if (uri.protocol) { + this.server = uri.protocol + ':' + this.server; + } - // assemble the endpoint from the uri pieces - globalServer = '//' + uri.host + - (uri.port ? ':' + uri.port : '') + - '/' + path + 'api/' + globalProject + '/store/'; + if (this.options.fetchContext) { + TK.remoteFetching = true; + } - if (uri.protocol) { - globalServer = uri.protocol + ':' + globalServer; - } + if (this.options.linesOfContext) { + TK.linesOfContext = this.options.linesOfContext; + } - if (globalOptions.fetchContext) { - TK.remoteFetching = true; - } + // 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 (!this.isSetup()) { + return; + } - if (globalOptions.linesOfContext) { - TK.linesOfContext = globalOptions.linesOfContext; - } + TK.report.subscribe(this.handleStackInfo); + + 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 (this.isFunction(options)) { + args = func || []; + func = options; + options = undefined; + } - // return for chaining - return Raven; - }, + 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) { + // options is optional + if (this.isFunction(options)) { + func = options; + options = undefined; + } - /* - * 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()) return; + return function() { + try { + return func.apply(this, arguments); + } catch (e) { + Raven.captureException(e, options); + throw e; + } + }; + }, + + /* + * Uninstalls the global error handler. + * + * @return {Raven} + */ + uninstall: function() { + TK.report.unsubscribe(this.handleStackInfo); + + 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 a string is passed through, recall as a message + if (typeof ex === 'string') { + return Raven.captureMessage(ex, options); + } - TK.report.subscribe(handleStackInfo); + // 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 { + TK.report(ex, options); + } catch (ex1) { + if (ex !== ex1) { + throw ex1; + } + } - return Raven; - }, + 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) { + // Fire away! + this.send( + this.arrayMerge({ + message: msg + }, 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} + */ + setUser: function(user) { + this.user = user; + + return Raven; + }, + + /**** Private functions ****/ + + uriKeys: 'source protocol authority userInfo user password host port relative path directory file query anchor'.split(' '), + uriPattern: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/, + + parseUri: function(str) { + var m = this.uriPattern.exec(str), + uri = {}, + i = 14; + + while (i--) { + uri[this.uriKeys[i]] = m[i] || ''; + } - /* - * 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 uri; + }, + + isUndefined: function(what) { + return typeof what === 'undefined'; + }, + + isFunction: function(what) { + return typeof what === 'function'; + }, + + each: function(obj, callback) { + var i, j; + + if (Raven.isUndefined(obj.length)) { + for (i in obj) { + if (obj.hasOwnProperty(i)) { + callback.call(this, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(this, i, obj[i]); + } + } + } + }, - Raven.wrap(options, func).apply(this, args); - }, + triggerEvent: function(eventType, options) { + var event, key; - /* - * 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) { - // options is optional - if (isFunction(options)) { - func = options; - options = undefined; - } + eventType = 'raven' + eventType[0].toUpperCase() + eventType.substr(1); - return function() { - try { - return func.apply(this, arguments); - } catch(e) { - Raven.captureException(e, options); - throw e; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventType, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventType; } - }; - }, - /* - * Uninstalls the global error handler. - * - * @return {Raven} - */ - uninstall: function() { - TK.report.unsubscribe(handleStackInfo); + if (typeof options !== 'object') { + options = {}; + } - return Raven; - }, + for (key in options) { + if (options.hasOwnProperty(key)) { + event[key] = options[key]; + } + } - /* - * 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 a string is passed through, recall as a message - if (typeof ex === 'string') { - return Raven.captureMessage(ex, options); - } + if (document.createEvent) { + document.dispatchEvent(event); + } else { + document.fireEvent('on' + event.eventType.toLowerCase(), event); + } + }, + + cachedAuth: null, - // 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 { - TK.report(ex, options); - } catch(ex1) { - if(ex !== ex1) { - throw ex1; + getAuthQueryString: function() { + if (this.cachedAuth) { + return this.cachedAuth; } - } - return Raven; - }, + var qs = [ + 'sentry_version=3', + 'sentry_client=raven-js/' + this.VERSION + ]; + if (this.key) { + qs.push('sentry_key=' + this.key); + } - /* - * 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) { - // Fire away! - send( - arrayMerge({ - message: msg - }, options) - ); + this.cachedAuth = '?' + qs.join('&'); + return this.cachedAuth; + }, - return Raven; - }, + normalizeFrame: function(frame) { + if (!frame.url) { + return; + } - /* - * Set/clear a user to be sent along with the payload. - * - * @param {object} user An object representing user data [optional] - * @return {Raven} - */ - setUser: function(user) { - globalUser = user; + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + 'function': frame.func || '?' + }, context = this.extractContextFromFrame(frame), i; + + if (context) { + var keys = ['pre_context', 'context_line', 'post_context']; + i = 3; + while (i--) { + normalized[keys[i]] = context[i]; + } + } - return Raven; - } -}; + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !this.options.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; + }, + + handleStackInfo: function(stackInfo, options) { + var frames = []; + + if (stackInfo.stack && stackInfo.stack.length) { + this.each(stackInfo.stack, this.hitch(this, function(i, stack) { + var frame = this.normalizeFrame(stack); + if (frame) { + frames.push(frame); + } + })); + } -function triggerEvent(eventType, options) { - var event, key; + this.triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); - eventType = 'raven' + eventType[0].toUpperCase() + eventType.substr(1); + this.processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); + }, + + extractContextFromFrame: function(frame) { + // immediately check if we should even attempt to parse a context + if (!frame.context || !this.options.fetchContext) { + return; + } - if (document.createEvent) { - event = document.createEvent('HTMLEvents'); - event.initEvent(eventType, true, true); - } else { - event = document.createEventObject(); - event.eventType = eventType; - } + 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 (typeof options !== 'object') { - options = {}; - } + if (isMinified) { + // The source is minified and we don't know which column. Fuck it. + if (this.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 + ]; + } - for (key in options) if (options.hasOwnProperty(key)) { - event[key] = options[key]; - } + return [ + context.slice(0, pivot), // pre_context + context[pivot], // context_line + context.slice(pivot + 1) // post_context + ]; + }, + + processException: function(type, message, fileurl, lineno, frames, options) { + var stacktrace, label, i; + + // IE8 really doesn't have Array.prototype.indexOf + // Filter out a message that matches our ignore list + i = this.options.ignoreErrors.length; + while (i--) { + if (message === this.options.ignoreErrors[i]) { + return; + } + } - if (document.createEvent) { - document.dispatchEvent(event); - } else { - document.fireEvent('on' + event.eventType.toLowerCase(), event); - } -} + if (frames && frames.length) { + stacktrace = {frames: frames}; + fileurl = fileurl || frames[0].filename; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno + }] + }; + } -var uriKeys = 'source protocol authority userInfo user password host port relative path directory file query anchor'.split(' '), - uriPattern = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + if (this.options.ignoreUrls && this.options.ignoreUrls.test(fileurl)) { + return; + } + if (this.options.whitelistUrls && !this.options.whitelistUrls.test(fileurl)) { + return; + } -/**** Private functions ****/ -function parseUri(str) { - var m = uriPattern.exec(str), - uri = {}, - i = 14; + label = lineno ? message + ' at ' + lineno : message; + + // Fire away! + this.send( + this.arrayMerge({ + 'sentry.interfaces.Exception': { + type: type, + value: message + }, + 'sentry.interfaces.Stacktrace': stacktrace, + culprit: fileurl, + message: label + }, options) + ); + }, + + arrayMerge: function(arr1, arr2) { + if (!arr2) { + return arr1; + } + this.each(arr2, function(key, value) { + arr1[key] = value; + }); + return arr1; + }, + + getHttpData: function() { + var http = { + url: document.location.href, + headers: { + 'User-Agent': navigator.userAgent + } + }; + + if (document.referrer) { + http.headers.Referer = document.referrer; + } - while (i--) uri[uriKeys[i]] = m[i] || ''; + return http; + }, - return uri; -} + send: function(data) { + if (!this.isSetup()) { + return; + } -function isUndefined(what) { - return typeof what === 'undefined'; -} + data = this.arrayMerge({ + project: this.project, + logger: Raven.options.logger, + site: Raven.options.site, + platform: 'javascript', + 'sentry.interfaces.Http': this.getHttpData() + }, data); -function isFunction(what) { - return typeof what === 'function'; -} + // Merge in the tags separately since arrayMerge doesn't handle a deep merge + data.tags = this.arrayMerge(Raven.options.tags, data.tags); -function each(obj, callback) { - var i, j; + // If there are no tags, strip the key from the payload alltogther. + if (!data.tags) { + delete data.tags; + } - if (isUndefined(obj.length)) { - for (i in obj) { - if (obj.hasOwnProperty(i)) { - callback.call(null, i, obj[i]); + if (this.user) { + data['sentry.interfaces.User'] = this.user; } - } - } else { - j = obj.length; - if (j) { - for (i = 0; i < j; i++) { - callback.call(null, i, obj[i]); + + if (this.isFunction(this.options.dataCallback)) { + data = this.options.dataCallback(data); } + + // Check if the request should be filtered or not + if (this.isFunction(this.options.shouldSendCallback) && !this.options.shouldSendCallback(data)) { + return; + } + + this.makeRequest(data); + }, + + hitch: function(scope, fn) { + return function() { + fn.apply(scope, arguments); + }; + }, + + makeRequest: function(data) { + var img, src; + + var success = function() { + this.triggerEvent('success', { + data: data, + src: src + }); + }; + + var failure = function() { + this.triggerEvent('failure', { + data: data, + src: src + }); + }; + + src = this.server + this.getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); + img = new Image(); + img.onload = this.hitch(this, success); + img.onerror = this.hitch(this, failure); + img.onabort = this.hitch(this, failure); + img.src = src; + }, + + isSetup: function() { + if (!this.hasJSON) { + return false; // needs JSON support + } + if (!this.server) { + if (global.console && console.error) { + console.error("Error: Raven has not been configured."); + } + return false; + } + return true; + }, + + wrapArguments: function(what) { + if (!this.isFunction(what)) { + return what; + } + + var wrapped = function() { + var args = [], i = arguments.length, arg; + while (i--) { + arg = arguments[i]; + args[i] = this.isFunction(arg) ? this.wrap(arg) : arg; + } + what.apply(null, args); + }; + + // copy over properties of the old function + for (var k in what) wrapped[k] = what[k]; + return wrapped; + }, + + joinRegExp: function(patterns) { + // Combine an array of regular expressions into one large regexp + var sources = [], i = patterns.length; + // lol, map + while (i--) { + sources[i] = patterns[i].source; + } + return new RegExp(sources.join('|'), 'i'); } - } -} - -var cachedAuth; - -function getAuthQueryString() { - if (cachedAuth) return cachedAuth; - - var qs = [ - 'sentry_version=3', - 'sentry_client=raven-js/' + Raven.VERSION - ]; - if (globalKey) { - qs.push('sentry_key=' + globalKey); - } - - cachedAuth = '?' + qs.join('&'); - return cachedAuth; -} - -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; - - // IE8 really doesn't have Array.prototype.indexOf - // Filter out a message that matches our ignore list - i = globalOptions.ignoreErrors.length; - while (i--) { - if (message === globalOptions.ignoreErrors[i]) { - return; - } - } - - if (frames && frames.length) { - stacktrace = {frames: frames}; - fileurl = fileurl || frames[0].filename; - } else if (fileurl) { - stacktrace = { - frames: [{ - filename: fileurl, - lineno: lineno - }] - }; - } - - 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( - arrayMerge({ - 'sentry.interfaces.Exception': { - type: type, - value: message - }, - 'sentry.interfaces.Stacktrace': stacktrace, - culprit: fileurl, - message: label - }, options) - ); -} - -function arrayMerge(arr1, arr2) { - if (!arr2) { - return arr1; - } - each(arr2, function(key, value){ - arr1[key] = value; - }); - return arr1; -} - -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 = arrayMerge({ - project: globalProject, - logger: globalOptions.logger, - site: globalOptions.site, - platform: 'javascript', - 'sentry.interfaces.Http': getHttpData() - }, data); - - // Merge in the tags separately since arrayMerge doesn't handle a deep merge - data.tags = arrayMerge(globalOptions.tags, data.tags); - - // If there are no tags, strip the key from the payload alltogther. - if (!data.tags) delete data.tags; - - if (globalUser) data['sentry.interfaces.User'] = globalUser; - - if (isFunction(globalOptions.dataCallback)) { - data = globalOptions.dataCallback(data); - } - - // Check if the request should be filtered or not - if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { - return; - } - - makeRequest(data); -} - - -function makeRequest(data) { - var img, src; - - function success() { - triggerEvent('success', { - data: data, - src: src - }); - } - - function failure() { - triggerEvent('failure', { - data: data, - src: src - }); - } - - src = globalServer + getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); - img = new Image(); - img.onload = success; - img.onerror = failure; - img.onabort = failure; - img.src = src; -} - -function isSetup() { - if (!hasJSON) return false; // needs JSON support - if (!globalServer) { - if (window.console && console.error) { - console.error("Error: Raven has not been configured."); - } - return false; - } - return true; -} - -function wrapArguments(what) { - if (!isFunction(what)) return what; - - function wrapped() { - var args = [], i = arguments.length, arg; - while(i--) { - arg = arguments[i]; - args[i] = isFunction(arg) ? Raven.wrap(arg) : arg; - } - what.apply(null, args); - } - // copy over properties of the old function - for (var k in what) wrapped[k] = what[k]; - return wrapped; -} - -function joinRegExp(patterns) { - // Combine an array of regular expressions into one large regexp - var sources = [], i = patterns.length; - // lol, map - while (i--) sources[i] = patterns[i].source; - return new RegExp(sources.join('|'), 'i'); -} - -Raven.afterLoad(); + // Exports + global.Raven = Raven; + global.TK = TK; + + // Legacy: + global.Raven.server = global.globalServer || global.Raven.server; + global.Raven.user = global.globalUser || global.Raven.user; + global.Raven.project = global.globalProject || global.Raven.project; + global.Raven.key = global.globalKey || global.Raven.key; + + // Boot. + global.Raven.afterLoad(); + +})(window); \ No newline at end of file diff --git a/test/raven.test.js b/test/raven.test.js index b1e9d8e94d11..b1b10139afac 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -1,10 +1,10 @@ function flushRavenState() { - cachedAuth = undefined; - hasJSON = !isUndefined(window.JSON); - globalServer = undefined; - globalUser = undefined; - globalProject = undefined; - globalOptions = { + Raven.cachedAuth = undefined; + Raven.hasJSON = !Raven.isUndefined(window.JSON); + Raven.server = undefined; + Raven.project = undefined; + Raven.user = undefined; + Raven.options = { logger: 'javascript', ignoreErrors: [], ignoreUrls: [], @@ -35,7 +35,7 @@ function setupRaven() { describe('globals', function() { beforeEach(function() { setupRaven(); - globalOptions.fetchContext = true; + Raven.options.fetchContext = true; }); afterEach(function() { @@ -51,7 +51,7 @@ describe('globals', function() { }); describe('getHttpData', function() { - var data = getHttpData(); + var data = Raven.getHttpData(); it('should have a url', function() { assert.equal(data.url, window.location.href); @@ -74,41 +74,41 @@ describe('globals', function() { describe('isUndefined', function() { it('should do as advertised', function() { - assert.isTrue(isUndefined()); - assert.isFalse(isUndefined({})); - assert.isFalse(isUndefined('')); - assert.isTrue(isUndefined(undefined)); + assert.isTrue(Raven.isUndefined()); + assert.isFalse(Raven.isUndefined({})); + assert.isFalse(Raven.isUndefined('')); + assert.isTrue(Raven.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)); + assert.isTrue(Raven.isFunction(function(){})); + assert.isFalse(Raven.isFunction({})); + assert.isFalse(Raven.isFunction('')); + assert.isFalse(Raven.isFunction(undefined)); }); }); describe('isSetup', function() { it('should return false with no JSON support', function() { - globalServer = 'http://localhost/'; - hasJSON = false; - assert.isFalse(isSetup()); + Raven.server = 'http://localhost/'; + Raven.hasJSON = false; + assert.isFalse(Raven.isSetup()); }); it('should return false when Raven is not configured and write to console.error', function() { - hasJSON = true; // be explicit - globalServer = undefined; + Raven.hasJSON = true; // be explicit + Raven.server = undefined; this.sinon.stub(console, 'error'); - assert.isFalse(isSetup()); + assert.isFalse(Raven.isSetup()); assert.isTrue(console.error.calledOnce); }); it('should return true when everything is all gravy', function() { - hasJSON = true; + Raven.hasJSON = true; setupRaven(); - assert.isTrue(isSetup()); + assert.isTrue(Raven.isSetup()); }); }); @@ -116,19 +116,19 @@ describe('globals', function() { it('should return a properly formatted string and cache it', function() { setupRaven(); var expected = '?sentry_version=3&sentry_client=raven-js/@VERSION&sentry_key=abc'; - assert.strictEqual(getAuthQueryString(), expected); - assert.strictEqual(cachedAuth, expected); + assert.strictEqual(Raven.getAuthQueryString(), expected); + assert.strictEqual(Raven.cachedAuth, expected); }); it('should return cached value when it exists', function() { - cachedAuth = 'lol'; - assert.strictEqual(getAuthQueryString(), 'lol'); + Raven.cachedAuth = 'lol'; + assert.strictEqual(Raven.getAuthQueryString(), 'lol'); }); }); describe('parseUri', function() { it('should do what it advertises', function() { - var pieces = parseUri(SENTRY_DSN); + var pieces = Raven.parseUri(SENTRY_DSN); assert.strictEqual(pieces.protocol, 'http'); assert.strictEqual(pieces.user, 'abc'); assert.strictEqual(pieces.port, '80'); @@ -144,7 +144,7 @@ describe('globals', function() { 'line2', // culprit ['line3'] // post ]; - this.sinon.stub(window, 'extractContextFromFrame').returns(context); + this.sinon.stub(Raven, 'extractContextFromFrame').returns(context); var frame = { url: 'http://example.com/path/file.js', line: 10, @@ -153,9 +153,9 @@ describe('globals', function() { // context: [] context is stubbed }; - globalOptions.fetchContext = true; + Raven.options.fetchContext = true; - assert.deepEqual(normalizeFrame(frame), { + assert.deepEqual(Raven.normalizeFrame(frame), { filename: 'http://example.com/path/file.js', lineno: 10, colno: 11, @@ -168,7 +168,7 @@ describe('globals', function() { }); it('should handle a frame without context', function() { - this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + this.sinon.stub(Raven, 'extractContextFromFrame').returns(undefined); var frame = { url: 'http://example.com/path/file.js', line: 10, @@ -177,9 +177,9 @@ describe('globals', function() { // context: [] context is stubbed }; - globalOptions.fetchContext = true; + Raven.options.fetchContext = true; - assert.deepEqual(normalizeFrame(frame), { + assert.deepEqual(Raven.normalizeFrame(frame), { filename: 'http://example.com/path/file.js', lineno: 10, colno: 11, @@ -189,7 +189,7 @@ describe('globals', function() { }); it('should not mark `in_app` if rules match', function() { - this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + this.sinon.stub(Raven, 'extractContextFromFrame').returns(undefined); var frame = { url: 'http://example.com/path/file.js', line: 10, @@ -198,10 +198,10 @@ describe('globals', function() { // context: [] context is stubbed }; - globalOptions.fetchContext = true; - globalOptions.includePaths = /^http:\/\/example\.com/; + Raven.options.fetchContext = true; + Raven.options.includePaths = /^http:\/\/example\.com/; - assert.deepEqual(normalizeFrame(frame), { + assert.deepEqual(Raven.normalizeFrame(frame), { filename: 'http://example.com/path/file.js', lineno: 10, colno: 11, @@ -211,7 +211,7 @@ describe('globals', function() { }); it('should mark `in_app` if rules do not match', function() { - this.sinon.stub(window, 'extractContextFromFrame').returns(undefined); + this.sinon.stub(Raven, 'extractContextFromFrame').returns(undefined); var frame = { url: 'http://lol.com/path/file.js', line: 10, @@ -220,10 +220,10 @@ describe('globals', function() { // context: [] context is stubbed }; - globalOptions.fetchContext = true; - globalOptions.includePaths = /^http:\/\/example\.com/; + Raven.options.fetchContext = true; + Raven.options.includePaths = /^http:\/\/example\.com/; - assert.deepEqual(normalizeFrame(frame), { + assert.deepEqual(Raven.normalizeFrame(frame), { filename: 'http://lol.com/path/file.js', lineno: 10, colno: 11, @@ -251,7 +251,7 @@ describe('globals', function() { 'line11' ] }; - var context = extractContextFromFrame(frame); + var context = Raven.extractContextFromFrame(frame); assert.deepEqual(context, [ ['line1', 'line2', 'line3', 'line4', 'line5'], 'culprit', @@ -263,7 +263,7 @@ describe('globals', function() { var frame = { column: 2 }; - assert.isUndefined(extractContextFromFrame(frame)); + assert.isUndefined(Raven.extractContextFromFrame(frame)); }); it('should reject a context if a line is too long without a column', function() { @@ -272,7 +272,7 @@ describe('globals', function() { new Array(1000).join('f') // generate a line that is 1000 chars long ] }; - assert.isUndefined(extractContextFromFrame(frame)); + assert.isUndefined(Raven.extractContextFromFrame(frame)); }); it('should reject a minified context with fetchContext disabled', function() { @@ -292,8 +292,8 @@ describe('globals', function() { 'line11' ] }; - globalOptions.fetchContext = false; - assert.isUndefined(extractContextFromFrame(frame)); + Raven.options.fetchContext = false; + assert.isUndefined(Raven.extractContextFromFrame(frame)); }); it('should truncate the minified line if there is a column number without sourcemaps enabled', function() { @@ -305,49 +305,49 @@ describe('globals', function() { 'aa' + (new Array(51).join('f')) + (new Array(500).join('z')) ] }; - assert.deepEqual(extractContextFromFrame(frame), [[], new Array(51).join('f'), []]); + assert.deepEqual(Raven.extractContextFromFrame(frame), [[], new Array(51).join('f'), []]); }); }); describe('processException', function() { it('should respect `ignoreErrors`', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, 'send'); - globalOptions.ignoreErrors = ['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); + Raven.options.ignoreErrors = ['e1', 'e2']; + Raven.processException('Error', 'e1', 'http://example.com', []); + assert.isFalse(Raven.send.called); + Raven.processException('Error', 'e2', 'http://example.com', []); + assert.isFalse(Raven.send.called); + Raven.processException('Error', 'error', 'http://example.com', []); + assert.isTrue(Raven.send.calledOnce); }); it('should respect `ignoreUrls`', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, '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); + Raven.options.ignoreUrls = Raven.joinRegExp([/.+?host1.+/, /.+?host2.+/]); + Raven.processException('Error', 'error', 'http://host1/', []); + assert.isFalse(Raven.send.called); + Raven.processException('Error', 'error', 'http://host2/', []); + assert.isFalse(Raven.send.called); + Raven.processException('Error', 'error', 'http://host3/', []); + assert.isTrue(Raven.send.calledOnce); }); it('should respect `whitelistUrls`', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, '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); + Raven.options.whitelistUrls = Raven.joinRegExp([/.+?host1.+/, /.+?host2.+/]); + Raven.processException('Error', 'error', 'http://host1/', []); + assert.equal(Raven.send.callCount, 1); + Raven.processException('Error', 'error', 'http://host2/', []); + assert.equal(Raven.send.callCount, 2); + Raven.processException('Error', 'error', 'http://host3/', []); + assert.equal(Raven.send.callCount, 2); }); it('should send a proper payload with frames', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, 'send'); var frames = [ { @@ -358,8 +358,8 @@ describe('globals', function() { } ]; - processException('Error', 'lol', 'http://example.com/override.js', 10, frames, {}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', 'http://example.com/override.js', 10, frames, {}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -371,8 +371,8 @@ describe('globals', function() { message: 'lol at 10' }]); - processException('Error', 'lol', '', 10, frames, {}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', '', 10, frames, {}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -384,8 +384,8 @@ describe('globals', function() { message: 'lol at 10' }]); - processException('Error', 'lol', '', 10, frames, {extra: 'awesome'}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', '', 10, frames, {extra: 'awesome'}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -400,10 +400,10 @@ describe('globals', function() { }); it('should send a proper payload without frames', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, 'send'); - processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -418,8 +418,8 @@ describe('globals', function() { message: 'lol at 10' }]); - processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', 'http://example.com/override.js', 10, [], {}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -434,8 +434,8 @@ describe('globals', function() { message: 'lol at 10' }]); - processException('Error', 'lol', 'http://example.com/override.js', 10, [], {extra: 'awesome'}); - assert.deepEqual(window.send.lastCall.args, [{ + Raven.processException('Error', 'lol', 'http://example.com/override.js', 10, [], {extra: 'awesome'}); + assert.deepEqual(Raven.send.lastCall.args, [{ 'sentry.interfaces.Exception': { type: 'Error', value: 'lol' @@ -455,30 +455,30 @@ describe('globals', function() { describe('send', function() { it('should check `isSetup`', function() { - this.sinon.stub(window, 'isSetup').returns(false); - this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(Raven, 'isSetup').returns(false); + this.sinon.stub(Raven, 'makeRequest'); - send(); - assert.isTrue(window.isSetup.calledOnce); - assert.isFalse(window.makeRequest.calledOnce); + Raven.send(); + assert.isTrue(Raven.isSetup.calledOnce); + assert.isFalse(Raven.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({ + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, 'makeRequest'); + this.sinon.stub(Raven, 'getHttpData').returns({ url: 'http://localhost/?a=b', headers: {'User-Agent': 'lolbrowser'} }); - globalProject = 2; - globalOptions = { + Raven.project = 2; + Raven.options = { logger: 'javascript', site: 'THE BEST' }; - send({foo: 'bar'}); - assert.deepEqual(window.makeRequest.lastCall.args[0], { + Raven.send({foo: 'bar'}); + assert.deepEqual(Raven.makeRequest.lastCall.args[0], { project: 2, logger: 'javascript', site: 'THE BEST', @@ -494,23 +494,23 @@ describe('globals', function() { }); 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({ + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, 'makeRequest'); + this.sinon.stub(Raven, 'getHttpData').returns({ url: 'http://localhost/?a=b', headers: {'User-Agent': 'lolbrowser'} }); - globalProject = 2; - globalOptions = { + Raven.project = 2; + Raven.options = { logger: 'javascript', site: 'THE BEST' }; - globalUser = {name: 'Matt'}; + Raven.user = {name: 'Matt'}; - send({foo: 'bar'}); - assert.deepEqual(window.makeRequest.lastCall.args, [{ + Raven.send({foo: 'bar'}); + assert.deepEqual(Raven.makeRequest.lastCall.args, [{ project: 2, logger: 'javascript', site: 'THE BEST', @@ -528,24 +528,23 @@ describe('globals', function() { }]); }); - 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({ + it('should merge in tags', function() { + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, 'makeRequest'); + this.sinon.stub(Raven, 'getHttpData').returns({ url: 'http://localhost/?a=b', headers: {'User-Agent': 'lolbrowser'} }); - globalProject = 2; - globalOptions = { + Raven.project = 2; + Raven.options = { logger: 'javascript', site: 'THE BEST', tags: {tag1: 'value1'} }; - - send({tags: {tag2: 'value2'}}); - assert.deepEqual(window.makeRequest.lastCall.args, [{ + Raven.send({tags: {tag2: 'value2'}}); + assert.deepEqual(Raven.makeRequest.lastCall.args, [{ project: 2, logger: 'javascript', site: 'THE BEST', @@ -561,10 +560,10 @@ describe('globals', function() { }); it('should let dataCallback override everything', function() { - this.sinon.stub(window, 'isSetup').returns(true); - this.sinon.stub(window, 'makeRequest'); + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, 'makeRequest'); - globalOptions = { + Raven.options = { projectId: 2, logger: 'javascript', site: 'THE BEST', @@ -573,10 +572,10 @@ describe('globals', function() { } }; - globalUser = {name: 'Matt'}; + Raven.user = {name: 'Matt'}; - send({foo: 'bar'}); - assert.deepEqual(window.makeRequest.lastCall.args, [{ + Raven.send({foo: 'bar'}); + assert.deepEqual(Raven.makeRequest.lastCall.args, [{ lol: 'ibrokeit' }]); }); @@ -585,10 +584,10 @@ describe('globals', function() { describe('makeRequest', function() { it('should load an Image', function() { imageCache = []; - this.sinon.stub(window, 'getAuthQueryString').returns('?lol'); - globalServer = 'http://localhost/'; + this.sinon.stub(Raven, 'getAuthQueryString').returns('?lol'); + Raven.server = 'http://localhost/'; - makeRequest({foo: 'bar'}); + Raven.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'); }); @@ -597,8 +596,8 @@ describe('globals', function() { 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'); + this.sinon.stub(Raven, 'normalizeFrame').returns(frame); + this.sinon.stub(Raven, 'processException'); var stackInfo = { name: 'Matt', @@ -610,14 +609,14 @@ describe('globals', function() { ] }; - handleStackInfo(stackInfo, {foo: 'bar'}); - assert.deepEqual(window.processException.lastCall.args, [ + Raven.handleStackInfo(stackInfo, {foo: 'bar'}); + assert.deepEqual(Raven.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'); + this.sinon.stub(Raven, 'makeRequest'); setupRaven(); var stackInfo = { name: 'Error', @@ -650,11 +649,11 @@ describe('globals', function() { ] }; - handleStackInfo(stackInfo, {foo: 'bar'}); - assert.isTrue(window.makeRequest.calledOnce); + Raven.handleStackInfo(stackInfo, {foo: 'bar'}); + assert.isTrue(Raven.makeRequest.calledOnce); /* This is commented out because chai is broken. - assert.deepEqual(window.makeRequest.lastCall.args, [{ + assert.deepEqual(Raven.makeRequest.lastCall.args, [{ project: 2, logger: 'javascript', platform: 'javascript', @@ -696,8 +695,8 @@ describe('globals', function() { }); it('should ignore frames that dont have a url', function() { - this.sinon.stub(window, 'normalizeFrame').returns(undefined); - this.sinon.stub(window, 'processException'); + this.sinon.stub(Raven, 'normalizeFrame').returns(undefined); + this.sinon.stub(Raven, 'processException'); var stackInfo = { name: 'Matt', @@ -707,15 +706,15 @@ describe('globals', function() { stack: new Array(2) }; - handleStackInfo(stackInfo, {foo: 'bar'}); - assert.deepEqual(window.processException.lastCall.args, [ + Raven.handleStackInfo(stackInfo, {foo: 'bar'}); + assert.deepEqual(Raven.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'); + this.sinon.stub(Raven, 'normalizeFrame').returns(undefined); + this.sinon.stub(Raven, 'processException'); var stackInfo = { name: 'Matt', @@ -725,9 +724,9 @@ describe('globals', function() { // stack: new Array(2) }; - handleStackInfo(stackInfo); - assert.isFalse(window.normalizeFrame.called); - assert.deepEqual(window.processException.lastCall.args, [ + Raven.handleStackInfo(stackInfo); + assert.isFalse(Raven.normalizeFrame.called); + assert.deepEqual(Raven.processException.lastCall.args, [ 'Matt', 'hey', 'http://example.com', 10, [], undefined ]); }); @@ -751,18 +750,18 @@ describe('Raven (public API)', function() { config: {some: 'config'} }; - this.sinon.stub(window, 'isSetup').returns(false); + this.sinon.stub(Raven, 'isSetup').returns(false); this.sinon.stub(TK.report, 'subscribe'); Raven.afterLoad(); - assert.equal(globalKey, 'random'); - assert.equal(globalServer, 'http://some.other.server:80/api/2/store/'); - assert.deepEqual(globalOptions.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); - assert.equal(globalOptions.some, 'config'); - assert.equal(globalProject, 2); + assert.equal(Raven.key, 'random'); + assert.equal(Raven.server, 'http://some.other.server:80/api/2/store/'); + assert.deepEqual(Raven.options.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); + assert.equal(Raven.options.some, 'config'); + assert.equal(Raven.project, 2); - assert.isTrue(window.isSetup.calledOnce); + assert.isTrue(Raven.isSetup.calledOnce); assert.isFalse(TK.report.subscribe.calledOnce); }); }); @@ -770,25 +769,25 @@ describe('Raven (public API)', function() { 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.deepEqual(globalOptions.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); - assert.equal(globalOptions.foo, 'bar'); - assert.equal(globalProject, 2); + assert.equal(Raven.key, 'abc'); + assert.equal(Raven.server, 'http://example.com:80/api/2/store/'); + assert.deepEqual(Raven.options.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); + assert.equal(Raven.options.foo, 'bar'); + assert.equal(Raven.project, 2); }); 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.deepEqual(globalOptions.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); - assert.equal(globalProject, 2); + assert.equal(Raven.key, 'abc'); + assert.equal(Raven.server, '//example.com/api/2/store/'); + assert.deepEqual(Raven.options.ignoreErrors, ['Script error.'], 'it should install "Script error." by default'); + assert.equal(Raven.project, 2); }); describe('whitelistUrls', function() { it('should be false if none are passed', function() { Raven.config('//abc@example.com/2'); - assert.equal(globalOptions.whitelistUrls, false); + assert.equal(Raven.options.whitelistUrls, false); }); it('should join into a single RegExp', function() { @@ -799,7 +798,7 @@ describe('Raven (public API)', function() { ] }); - assert.match(globalOptions.whitelistUrls, /my.app|other.app/i); + assert.match(Raven.options.whitelistUrls, /my.app|other.app/i); }); it('should handle strings as well', function() { @@ -810,26 +809,26 @@ describe('Raven (public API)', function() { ] }); - assert.match(globalOptions.whitelistUrls, /my.app|stringy.app/i); + assert.match(Raven.options.whitelistUrls, /my.app|stringy.app/i); }); }); }); describe('.install', function() { it('should check `isSetup`', function() { - this.sinon.stub(window, 'isSetup').returns(false); + this.sinon.stub(Raven, 'isSetup').returns(false); this.sinon.stub(TK.report, 'subscribe'); Raven.install(); - assert.isTrue(window.isSetup.calledOnce); + assert.isTrue(Raven.isSetup.calledOnce); assert.isFalse(TK.report.subscribe.calledOnce); }); it('should register itself with TraceKit', function() { - this.sinon.stub(window, 'isSetup').returns(true); + this.sinon.stub(Raven, 'isSetup').returns(true); this.sinon.stub(TK.report, 'subscribe'); assert.equal(Raven, Raven.install()); assert.isTrue(TK.report.subscribe.calledOnce); - assert.equal(TK.report.subscribe.lastCall.args[0], handleStackInfo); + assert.equal(TK.report.subscribe.lastCall.args[0], Raven.handleStackInfo); }); }); @@ -907,28 +906,28 @@ describe('Raven (public API)', function() { this.sinon.stub(TK.report, 'unsubscribe'); Raven.uninstall(); assert.isTrue(TK.report.unsubscribe.calledOnce); - assert.equal(TK.report.unsubscribe.lastCall.args[0], handleStackInfo); + assert.equal(TK.report.unsubscribe.lastCall.args[0], Raven.handleStackInfo); }); }); describe('.setUser', function() { - it('should set the globalUser object', function() { + it('should set the user object', function() { Raven.setUser({name: 'Matt'}); - assert.deepEqual(globalUser, {name: 'Matt'}); + assert.deepEqual(Raven.user, {name: 'Matt'}); }); - it('should clear the globalUser with no arguments', function() { - globalUser = {name: 'Matt'}; + it('should clear the user with no arguments', function() { + Raven.user = {name: 'Matt'}; Raven.setUser(); - assert.isUndefined(globalUser); + assert.isUndefined(Raven.user); }); }); describe('.captureMessage', function() { it('should work as advertised', function() { - this.sinon.stub(window, 'send'); + this.sinon.stub(Raven, 'send'); Raven.captureMessage('lol', {foo: 'bar'}); - assert.deepEqual(window.send.lastCall.args, [{ + assert.deepEqual(Raven.send.lastCall.args, [{ message: 'lol', foo: 'bar' }]);