raygun4js
Version:
Raygun.io plugin for JavaScript
944 lines (778 loc) • 32.8 kB
JavaScript
/*
* raygun4js
* https://github.com/MindscapeHQ/raygun4js
*
* Copyright (c) 2013-2017 Raygun Limited
* Licensed under the MIT license.
*/
/*globals __DEV__, raygunUtilityFactory, raygunBreadcrumbsFactory */
var raygunFactory = function (window, $, forBreadcrumbs, undefined) {
var Raygun = {};
Raygun.Utilities = raygunUtilityFactory(window, Raygun);
Raygun.Breadcrumbs = raygunBreadcrumbsFactory(window, Raygun);
// Constants
var ProviderStates = {
LOADING: 0,
READY: 1
};
var _userKey = 'raygun4js-userid';
// State variables
var _traceKit = TraceKit,
_raygun = window.Raygun,
_debugMode = false,
_allowInsecureSubmissions = false,
_ignoreAjaxAbort = false,
_ignoreAjaxError = false,
_enableOfflineSave = false,
_ignore3rdPartyErrors = false,
_disableAnonymousUserTracking = false,
_disableErrorTracking = false,
_disablePulse = true,
_wrapAsynchronousCallbacks = false,
_customData = {},
_tags = [],
_user,
_version,
_filteredKeys,
_whitelistedScriptDomains = [],
_beforeSendCallback,
_groupingKeyCallback,
_beforeXHRCallback,
_afterSendCallback,
_raygunApiUrl = 'https://api.raygun.io',
_excludedHostnames = null,
_excludedUserAgents = null,
_filterScope = 'customData',
_rum = null,
_breadcrumbs = new Raygun.Breadcrumbs(),
_pulseMaxVirtualPageDuration = null,
_pulseIgnoreUrlCasing = true,
_providerState = ProviderStates.LOADING,
_loadedFrom,
_processExceptionQueue = [],
_trackEventQueue = [],
_pulseCustomLoadTimeEnabled = null,
$document;
var rand = Math.random();
var _publicRaygunFunctions =
{
Rand: rand,
Options: { },
noConflict: function () {
// Because _raygun potentially gets set before other code sets window.Raygun
// this will potentially overwrite the new Raygun object with undefined
// Not really much point in restoring undefined so just don't do that
if (_raygun) {
window.Raygun = _raygun;
}
return Raygun;
},
constructNewRaygun: function (forBreadcrumbs) {
var rgInstance = raygunFactory(window, window.jQuery, forBreadcrumbs);
return rgInstance;
},
init: function (key, options, customdata) {
_traceKit.remoteFetching = false;
this.Options._raygunApiKey = key;
if (customdata) {
_customData = customdata;
}
if ($) {
$document = $(document);
}
if (options) {
_allowInsecureSubmissions = options.allowInsecureSubmissions || false;
_ignoreAjaxAbort = options.ignoreAjaxAbort || false;
_ignoreAjaxError = options.ignoreAjaxError || false;
_disableAnonymousUserTracking = options.disableAnonymousUserTracking || false;
_disableErrorTracking = options.disableErrorTracking || false;
_disablePulse = options.disablePulse === undefined ? true : options.disablePulse;
_excludedHostnames = options.excludedHostnames || false;
_excludedUserAgents = options.excludedUserAgents || false;
_pulseMaxVirtualPageDuration = options.pulseMaxVirtualPageDuration || null;
_pulseIgnoreUrlCasing = options.pulseIgnoreUrlCasing || false;
_pulseCustomLoadTimeEnabled = options.pulseCustomLoadTimeEnabled || false;
if (options.apiUrl) {
_raygunApiUrl = options.apiUrl;
}
if (typeof options.wrapAsynchronousCallbacks !== 'undefined') {
_wrapAsynchronousCallbacks = options.wrapAsynchronousCallbacks;
}
if (options.debugMode) {
_debugMode = options.debugMode;
}
this.Options._debugMode = _debugMode;
if (options.ignore3rdPartyErrors) {
_ignore3rdPartyErrors = true;
}
if (options.apiEndpoint) {
_raygunApiUrl = options.apiEndpoint;
}
if (options.from) {
_loadedFrom = options.from;
}
}
if (!forBreadcrumbs) {
_breadcrumbs.setCrashReportingInstance(this.constructNewRaygun(true));
}
ensureUser();
return Raygun;
},
withCustomData: function (customdata) {
_customData = customdata;
return Raygun;
},
withTags: function (tags) {
_tags = tags;
if (_rum !== undefined && _rum !== null) {
_rum.withTags(tags);
}
return Raygun;
},
attach: function () {
if (!Raygun.Utilities.isApiKeyConfigured() || _disableErrorTracking) {
return Raygun;
}
if (window.RaygunObject && window[window.RaygunObject] && window[window.RaygunObject].q) {
window.onerror = null;
}
// Attach React Native's handler in Release mode
if (Raygun.Utilities.isReactNative()) {
if (__DEV__ !== true && window.ErrorUtils && window.ErrorUtils.setGlobalHandler) {
window.ErrorUtils.setGlobalHandler(function (error, fatal) {
// Calling the defaultReactNativeGlobalHandler in release mode instantly closes the application
// If an exception is currently being sent it will be lost, this sets our own afterSendCallback
// to notify us when the error is done sending so we can call the default handler
var originalAfterSendCallback = _afterSendCallback;
_afterSendCallback = function () {
if (typeof originalAfterSendCallback === 'function') {
originalAfterSendCallback();
}
Raygun.Utilities.defaultReactNativeGlobalHandler(error, fatal);
_afterSendCallback = originalAfterSendCallback;
};
TraceKit.report(error);
});
}
}
_traceKit.report.subscribe(processException);
if (_wrapAsynchronousCallbacks) {
_traceKit.extendToAsynchronousCallbacks();
}
if ($document && $document.ajaxError && !_ignoreAjaxError) {
$document.ajaxError(processJQueryAjaxError);
}
return Raygun;
},
detach: function () {
_traceKit.report.unsubscribe(processException);
if ($document) {
$document.unbind('ajaxError', processJQueryAjaxError);
}
return Raygun;
},
send: function (ex, customData, tags) {
if (_disableErrorTracking) {
Raygun.Utilities.log('Error not sent due to disabled error tracking');
return Raygun;
}
try {
processException(
_traceKit.computeStackTrace(ex),
{
customData: typeof _customData === 'function' ?
Raygun.Utilities.merge(_customData(), customData) :
Raygun.Utilities.merge(_customData, customData),
tags: typeof _tags === 'function' ?
Raygun.Utilities.mergeArray(_tags(), tags) :
Raygun.Utilities.mergeArray(_tags, tags)
},
true
);
}
catch (traceKitException) {
if (ex !== traceKitException) {
throw traceKitException;
}
}
return Raygun;
},
setUser: function (user, isAnonymous, email, fullName, firstName, uuid) {
_user = {
'Identifier': user
};
if (typeof isAnonymous === 'boolean') {
_user['IsAnonymous'] = isAnonymous;
}
if (email) {
_user['Email'] = email;
}
if (fullName) {
_user['FullName'] = fullName;
}
if (firstName) {
_user['FirstName'] = firstName;
}
if (uuid) {
_user['UUID'] = uuid;
}
if (_rum !== undefined && _rum !== null) {
_rum.setUser(_user);
}
return Raygun;
},
resetAnonymousUser: function () {
Raygun.Utilities.clearCookie('raygun4js-userid');
},
setVersion: function (version) {
_version = version;
return Raygun;
},
saveIfOffline: function (enableOffline) {
if (typeof enableOffline !== 'undefined' && typeof enableOffline === 'boolean') {
_enableOfflineSave = enableOffline;
}
return Raygun;
},
filterSensitiveData: function (filteredKeys) {
_filteredKeys = filteredKeys;
return Raygun;
},
setFilterScope: function (scope) {
if (scope === 'customData' || scope === 'all') {
_filterScope = scope;
}
return Raygun;
},
whitelistCrossOriginDomains: function (whitelist) {
_whitelistedScriptDomains = whitelist;
return Raygun;
},
onBeforeSend: function (callback) {
_beforeSendCallback = callback;
return Raygun;
},
groupingKey: function (callback) {
_groupingKeyCallback = callback;
return Raygun;
},
onBeforeXHR: function (callback) {
_beforeXHRCallback = callback;
return Raygun;
},
onAfterSend: function (callback) {
_afterSendCallback = callback;
return Raygun;
},
// Public Pulse functions
endSession: function () {
if (Raygun.RealUserMonitoring !== undefined && _rum) {
_rum.endSession();
}
},
trackEvent: function (type, options) {
if (_providerState !== ProviderStates.READY) {
_trackEventQueue.push({ type: type, options: options });
return;
}
if (Raygun.RealUserMonitoring !== undefined && _rum) {
if (type === 'pageView' && options.path) {
_rum.virtualPageLoaded(options.path);
} else if (type === 'customTimings' && options.timings) {
_rum.sendCustomTimings(options.timings);
}
}
},
recordBreadcrumb: function() {
_breadcrumbs.recordBreadcrumb.apply(_breadcrumbs, arguments);
},
enableAutoBreadcrumbs: function(type) {
if (type) {
_breadcrumbs['enableAutoBreadcrumbs' + type]();
} else {
_breadcrumbs.enableAutoBreadcrumbs();
}
},
disableAutoBreadcrumbs: function(type) {
if (type) {
_breadcrumbs['disableAutoBreadcrumbs' + type]();
} else {
_breadcrumbs.disableAutoBreadcrumbs();
}
},
setBreadcrumbOption: function(option, value) {
_breadcrumbs.setOption(option, value);
},
setBreadcrumbs: function(breadcrumbs) {
_breadcrumbs = breadcrumbs;
}
};
Raygun = Raygun.Utilities.mergeMutate(Raygun, _publicRaygunFunctions);
function callAfterSend(response) {
if (typeof _afterSendCallback === 'function') {
_afterSendCallback(response);
}
}
function ensureUser() {
if (!_user && !_disableAnonymousUserTracking) {
Raygun.Utilities.readCookie(_userKey, setUserComplete);
} else {
bootRaygun();
}
}
function setUserComplete(error, userId) {
var userIdentifier;
if (error) {
userIdentifier = "Unknown";
}
if (!userId) {
userIdentifier = Raygun.Utilities.getUuid();
Raygun.Utilities.createCookie(_userKey, userIdentifier, 24 * 31);
} else {
userIdentifier = userId;
}
Raygun.setUser(userIdentifier, true, null, null, null, userIdentifier);
bootRaygun();
}
// The final initializing logic is provided as a callback due to async storage methods for user data in React Native
// The common case executes it immediately due to that data being provided by the cookie synchronously
// The case when Affected User Tracking is enabled calls this function when the code sets the user data
function bootRaygun() {
if (_providerState === ProviderStates.READY) {
return;
}
_providerState = ProviderStates.READY;
if (Raygun.RealUserMonitoring !== undefined && !_disablePulse) {
var startRum = function () {
_rum = new Raygun.RealUserMonitoring(Raygun.Options._raygunApiKey, _raygunApiUrl, makePostCorsRequest, _user, _version, _tags, _excludedHostnames, _excludedUserAgents, _debugMode, _pulseMaxVirtualPageDuration, _pulseIgnoreUrlCasing, _pulseCustomLoadTimeEnabled);
_rum.attach();
};
if (_loadedFrom === 'onLoad') {
startRum();
} else {
if (window.addEventListener) {
window.addEventListener('load', startRum);
} else {
window.attachEvent('onload', startRum);
}
}
}
retriggerDelayedCommands();
sendSavedErrors();
}
// We need to delay handled/unhandled send() and trackEvent() calls until the user data callback has returned
function retriggerDelayedCommands() {
var i;
for (i = 0; i < _processExceptionQueue.length; i++) {
processException(_processExceptionQueue[i].stackTrace, _processExceptionQueue[i].options, _processExceptionQueue[i].userTriggered);
}
_processExceptionQueue = [];
for (i = 0; i < _trackEventQueue.length; i++) {
_rum.trackEvent(_trackEventQueue[i].type, _trackEventQueue[i].options);
}
_trackEventQueue = [];
}
function offlineSave(url, data) {
var dateTime = new Date().toJSON();
try {
var key = 'raygunjs+' + Raygun.Options._raygunApiKey + '=' + dateTime + '=' + Raygun.Utilities.getRandomInt();
if (typeof localStorage[key] === 'undefined') {
localStorage[key] = JSON.stringify({url: url, data: data});
}
} catch (e) {
Raygun.Utilities.log('Raygun4JS: LocalStorage full, cannot save exception');
}
}
function sendSavedErrors() {
if (Raygun.Utilities.localStorageAvailable()) {
for (var key in localStorage) {
// TODO: Remove (0,9) substring after a given amount of time, only there for legacy reasons
if (key.substring(0, 9) === 'raygunjs=' || key.substring(0, 33) === 'raygunjs+' + Raygun.Options._raygunApiKey) {
try {
var payload = JSON.parse(localStorage[key]);
makePostCorsRequest(payload.url, payload.data);
} catch (e) {
Raygun.Utilities.log('Raygun4JS: Invalid JSON object in LocalStorage');
}
try {
localStorage.removeItem(key);
} catch (e) {
Raygun.Utilities.log('Raygun4JS: Unable to remove error');
}
}
}
}
}
function filterValue(key, value) {
if (_filteredKeys) {
for (var i = 0; i < _filteredKeys.length; i++) {
if (typeof _filteredKeys[i] === 'object' && typeof _filteredKeys[i].exec === 'function') {
if (_filteredKeys[i].exec(key) !== null) {
return '[removed by filter]';
}
}
else if (_filteredKeys[i] === key) {
return '[removed by filter]';
}
}
}
return value;
}
function filterObject(reference, parentKey) {
if (reference == null) {
return reference;
}
if (Object.prototype.toString.call(reference) !== '[object Object]') {
return reference;
}
var filteredObject = {};
for (var propertyName in reference) {
var propertyValue = reference[propertyName];
if (Object.prototype.toString.call(propertyValue) === '[object Object]') {
if (parentKey !== 'Details' || propertyName !== 'Client') {
filteredObject[propertyName] = filterObject(filterValue(propertyName, propertyValue), propertyName);
} else {
filteredObject[propertyName] = propertyValue;
}
} else if (Object.prototype.toString.call(propertyValue) !== '[object Function]') {
if (typeof parentKey !== 'undefined') {
filteredObject[propertyName] = filterValue(propertyName, propertyValue);
} else if (propertyName === 'OccurredOn') {
filteredObject[propertyName] = propertyValue;
}
}
}
return filteredObject;
}
function processJQueryAjaxError(event, jqXHR, ajaxSettings, thrownError) {
var message = 'AJAX Error: ' +
(jqXHR.statusText || 'unknown') + ' ' +
(ajaxSettings.type || 'unknown') + ' ' +
(Raygun.Utilities.truncateURL(ajaxSettings.url) || 'unknown');
// ignore ajax abort if set in the options
if (_ignoreAjaxAbort) {
if (jqXHR.status === 0 || !jqXHR.getAllResponseHeaders()) {
return;
}
}
Raygun.send(thrownError || event.type, {
status: jqXHR.status,
statusText: jqXHR.statusText,
type: ajaxSettings.type,
url: ajaxSettings.url,
ajaxErrorMessage: message,
contentType: ajaxSettings.contentType,
requestData: ajaxSettings.data && ajaxSettings.data.slice ? ajaxSettings.data.slice(0, 10240) : undefined,
responseData: jqXHR.responseText && jqXHR.responseText.slice ? jqXHR.responseText.slice(0, 10240) : undefined,
activeTarget: event.target && event.target.activeElement && event.target.activeElement.outerHTML && event.target.activeElement.outerHTML.slice ? event.target.activeElement.outerHTML.slice(0, 10240) : undefined
});
}
function processException(stackTrace, options, userTriggered) {
if (_providerState !== ProviderStates.READY) {
_processExceptionQueue.push({ stackTrace: stackTrace, options: options, userTriggered: userTriggered });
return;
}
var scriptError = 'Script error';
var stack = [],
qs = {};
if (_ignore3rdPartyErrors) {
if (!stackTrace.stack || !stackTrace.stack.length) {
Raygun.Utilities.log('Raygun4JS: Cancelling send due to null stacktrace');
return;
}
var domain = Raygun.Utilities.parseUrl('domain');
var msg = scriptError;
if (stackTrace.message) {
msg = stackTrace.message;
} else if (options && options.status) {
msg = options.status;
}
if (typeof msg === 'undefined') {
msg = scriptError;
}
if (!Raygun.Utilities.isReactNative() &&
msg.substring(0, scriptError.length) === scriptError &&
stackTrace.stack[0].url !== null &&
stackTrace.stack[0].url.indexOf(domain) === -1 &&
(stackTrace.stack[0].line === 0 || stackTrace.stack[0].func === '?')) {
Raygun.Utilities.log('Raygun4JS: cancelling send due to third-party script error with no stacktrace and message');
return;
}
if (stackTrace.stack[0].url !== null && stackTrace.stack[0].url.indexOf(domain) === -1) {
var allowedDomainFound = false;
for (var i in _whitelistedScriptDomains) {
if (stackTrace.stack[0].url.indexOf(_whitelistedScriptDomains[i]) > -1) {
allowedDomainFound = true;
}
}
if (!allowedDomainFound) {
Raygun.Utilities.log('Raygun4JS: cancelling send due to error on non-origin, non-whitelisted domain');
return;
}
}
}
if (_excludedHostnames instanceof Array) {
for (var hostIndex in _excludedHostnames) {
if (_excludedHostnames.hasOwnProperty(hostIndex)) {
if (window.location.hostname && window.location.hostname.match(_excludedHostnames[hostIndex])) {
Raygun.Utilities.log('Raygun4JS: cancelling send as error originates from an excluded hostname');
return;
}
}
}
}
if (_excludedUserAgents instanceof Array && !Raygun.Utilities.isReactNative()) {
for (var userAgentIndex in _excludedUserAgents) {
if (_excludedUserAgents.hasOwnProperty(userAgentIndex)) {
if (navigator.userAgent.match(_excludedUserAgents[userAgentIndex])) {
Raygun.Utilities.log('Raygun4JS: cancelling send as error originates from an excluded user agent');
return;
}
}
}
}
if (!Raygun.Utilities.isReactNative() && navigator.userAgent.match("RaygunPulseInsightsCrawler"))
{
return;
}
if (stackTrace.stack && stackTrace.stack.length) {
Raygun.Utilities.forEach(stackTrace.stack, function (i, frame) {
stack.push({
'LineNumber': frame.line,
'ColumnNumber': frame.column,
'ClassName': 'line ' + frame.line + ', column ' + frame.column,
'FileName': frame.url,
'MethodName': frame.func || '[anonymous]'
});
});
}
var queryString = Raygun.Utilities.parseUrl('?');
if (queryString.length > 0) {
Raygun.Utilities.forEach(queryString.split('&'), function (i, segment) {
var parts = segment.split('=');
if (parts && parts.length === 2) {
var key = decodeURIComponent(parts[0]);
var value = filterValue(key, parts[1]);
qs[key] = value;
}
});
}
if (options === undefined) {
options = {};
}
if (Raygun.Utilities.isEmpty(options.customData)) {
if (typeof _customData === 'function') {
options.customData = _customData();
} else {
options.customData = _customData;
}
}
if (Raygun.Utilities.isEmpty(options.tags)) {
if (typeof _tags === 'function') {
options.tags = _tags();
} else {
options.tags = _tags;
}
}
if (!userTriggered) {
if (!options.tags) {
options.tags = [];
}
if (!Raygun.Utilities.contains(options.tags, 'UnhandledException')) {
options.tags.push('UnhandledException');
}
}
if (Raygun.Utilities.isReactNative() && !Raygun.Utilities.contains(options.tags, 'React Native')) {
options.tags.push('React Native');
}
var screenData = window.screen || {width: Raygun.Utilities.getViewPort().width, height: Raygun.Utilities.getViewPort().height, colorDepth: 8};
var custom_message = options.customData && options.customData.ajaxErrorMessage;
var finalCustomData;
if (_filterScope === 'customData') {
finalCustomData = filterObject(options.customData, 'UserCustomData');
} else {
finalCustomData = options.customData;
}
try {
JSON.stringify(finalCustomData);
} catch (e) {
var m = 'Cannot add custom data; may contain circular reference';
finalCustomData = {error: m};
Raygun.Utilities.log('Raygun4JS: ' + m);
}
var finalMessage = scriptError;
if (custom_message) {
finalMessage = custom_message;
} else if (stackTrace.message) {
finalMessage = stackTrace.message;
} else if (options && options.status) {
finalMessage = options.status;
}
if (typeof finalMessage === 'undefined') {
finalMessage = scriptError;
}
if (finalMessage && (typeof finalMessage === 'string')) {
finalMessage = finalMessage.substring(0, 512);
}
var pageLocation;
if (!Raygun.Utilities.isReactNative()) {
pageLocation = [location.protocol, '//', location.host, location.pathname, location.hash].join('');
} else {
pageLocation = '/';
}
var payload = {
'OccurredOn': new Date(),
'Details': {
'Error': {
'ClassName': stackTrace.name,
'Message': finalMessage,
'StackTrace': stack,
'StackString': stackTrace.stackstring
},
'Environment': {
'UtcOffset': new Date().getTimezoneOffset() / -60.0,
'User-Language': navigator.userLanguage,
'Document-Mode': !Raygun.Utilities.isReactNative() ? document.documentMode : 'Not available',
'Browser-Width': Raygun.Utilities.getViewPort().width,
'Browser-Height': Raygun.Utilities.getViewPort().height,
'Screen-Width': screenData.width,
'Screen-Height': screenData.height,
'Color-Depth': screenData.colorDepth,
'Browser': navigator.appCodeName,
'Browser-Name': navigator.appName,
'Browser-Version': navigator.appVersion,
'Platform': navigator.platform
},
'Client': {
'Name': 'raygun-js',
'Version': '{{VERSION}}'
},
'UserCustomData': finalCustomData,
'Tags': options.tags,
'Request': {
'Url': pageLocation,
'QueryString': qs,
'Headers': {
'User-Agent': navigator.userAgent,
'Referer': !Raygun.Utilities.isReactNative() ? document.referrer : 'Not available',
'Host': !Raygun.Utilities.isReactNative() ? document.domain : 'Not available'
}
},
'Version': _version || 'Not supplied'
}
};
payload.Details.User = _user;
if (_breadcrumbs.any()) {
payload.Details.Breadcrumbs = [];
var crumbs = _breadcrumbs.all();
crumbs.forEach(function(crumb) {
if (crumb.metadata) {
crumb.CustomData = crumb.metadata;
delete crumb.metadata;
}
payload.Details.Breadcrumbs.push(crumb);
});
}
if (_filterScope === 'all') {
payload = filterObject(payload);
}
if (typeof _groupingKeyCallback === 'function') {
Raygun.Utilities.log('Raygun4JS: calling custom grouping key');
payload.Details.GroupingKey = _groupingKeyCallback(payload, stackTrace, options);
}
if (typeof _beforeSendCallback === 'function') {
var mutatedPayload = _beforeSendCallback(payload);
if (mutatedPayload) {
sendToRaygun(mutatedPayload);
}
} else {
sendToRaygun(payload);
}
}
function sendToRaygun(data) {
if (!Raygun.Utilities.isApiKeyConfigured()) {
return;
}
Raygun.Utilities.log('Sending exception data to Raygun:', data);
var url = _raygunApiUrl + '/entries?apikey=' + encodeURIComponent(Raygun.Options._raygunApiKey);
makePostCorsRequest(url, JSON.stringify(data));
}
// Create the XHR object.
function createCORSRequest(method, url) {
var xhr;
xhr = new window.XMLHttpRequest();
if ("withCredentials" in xhr || Raygun.Utilities.isReactNative()) {
// XHR for Chrome/Firefox/Opera/Safari
// as well as React Native's custom XHR implementation
xhr.open(method, url, true);
} else if (window.XDomainRequest) {
// XDomainRequest for IE.
if (_allowInsecureSubmissions) {
// remove 'https:' and use relative protocol
// this allows IE8 to post messages when running
// on http
url = url.slice(6);
}
xhr = new window.XDomainRequest();
xhr.open(method, url);
}
xhr.timeout = 10000;
return xhr;
}
// Make the actual CORS request.
function makePostCorsRequest(url, data) {
var xhr = createCORSRequest('POST', url, data);
if (typeof _beforeXHRCallback === 'function') {
_beforeXHRCallback(xhr);
}
Raygun.Utilities.log("Is offline enabled? " + _enableOfflineSave);
// For some reason this check is false in React Native but these handlers still need to be attached
if ('withCredentials' in xhr || Raygun.Utilities.isReactNative()) {
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status === 202) {
sendSavedErrors();
} else if (_enableOfflineSave && xhr.status !== 403 && xhr.status !== 400 && xhr.status !== 429) {
offlineSave(url, data);
}
};
xhr.onload = function () {
Raygun.Utilities.log('posted to Raygun');
callAfterSend(this);
};
} else if (window.XDomainRequest) {
xhr.ontimeout = function () {
if (_enableOfflineSave) {
Raygun.Utilities.log('Raygun: saved locally');
offlineSave(url, data);
}
};
xhr.onload = function () {
Raygun.Utilities.log('posted to Raygun');
sendSavedErrors();
callAfterSend(this);
};
}
xhr.onerror = function () {
Raygun.Utilities.log('failed to post to Raygun');
callAfterSend(this);
};
if (!xhr) {
Raygun.Utilities.log('CORS not supported');
return;
}
// Old versions of RN fail to send errors without this
if (Raygun.Utilities.isReactNative()) {
xhr.setRequestHeader("Content-type", "application/json;charset=UTF-8");
}
xhr.send(data);
}
if (!window.__raygunNoConflict && !forBreadcrumbs) {
window.Raygun = Raygun;
}
TraceKit.setRaygun(Raygun);
return Raygun;
};
window.__instantiatedRaygun = raygunFactory(window, window.jQuery);