text: '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) {
          // 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;
      },
  
      /*
       * 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 typeof what === 'undefined';
  }
  
  function isFunction(what) {
      return typeof what === 'function';
  }
  
  function isString(what) {
      return typeof what === '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;
      }
  
      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;
      }
  
      // 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 = new Image(),
          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;
  }
  
  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();
  