raygun4js
Version:
Raygun.io plugin for JavaScript
1,105 lines (934 loc) • 32.5 kB
JavaScript
/**
* @prettier
*/
/*
* raygun4js
* https://github.com/MindscapeHQ/raygun4js
*
* Copyright (c) 2013-2018 Raygun Limited
* Licensed under the MIT license.
*/
/*globals __DEV__, raygunUtilityFactory, raygunBreadcrumbsFactory */
var raygunFactory = function(window, $, 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,
_beforeSendRumCallback,
_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,
_captureUnhandledRejections = true,
_setCookieAsSecure = false,
detachPromiseRejectionFunction;
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() {
var rgInstance = raygunFactory(window, window.jQuery);
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;
_setCookieAsSecure = options.setCookieAsSecure || false;
if (options.apiUrl) {
_raygunApiUrl = options.apiUrl;
}
if (typeof options.wrapAsynchronousCallbacks !== 'undefined') {
_wrapAsynchronousCallbacks = options.wrapAsynchronousCallbacks;
}
if(typeof options.captureUnhandledRejections !== 'undefined') {
_captureUnhandledRejections = options.captureUnhandledRejections;
}
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;
}
}
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;
}
if (_captureUnhandledRejections) {
attachPromiseRejectionHandler();
}
// 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);
}
if (_captureUnhandledRejections) {
detachPromiseRejectionHandler();
}
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,
ex
);
} 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;
},
onBeforeSendRum: function(callback) {
_beforeSendRumCallback = 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;
},
getBreadcrumbs: function() {
return _breadcrumbs.all();
},
};
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, _setCookieAsSecure);
} else {
userIdentifier = userId;
}
Raygun.setUser(userIdentifier, true, null, null, null, userIdentifier);
bootRaygun();
}
// Callback for `unhandledrejection` event.
function promiseRejectionHandler(event) {
var error = event.reason;
if(!error && event.detail && event.detail.reason) {
error = event.detail.reason;
}
if(!error) {
error = event;
}
if(typeof error !== Error && typeof error === "string") {
error = new Error(error);
}
_publicRaygunFunctions.send(error, undefined, ['UnhandledPromiseRejection']);
}
// Install global promise rejection handler.
function attachPromiseRejectionHandler() {
detachPromiseRejectionFunction = Raygun.Utilities.addEventHandler(window, 'unhandledrejection', promiseRejectionHandler);
}
// Uninstall global promise rejection handler.
function detachPromiseRejectionHandler() {
detachPromiseRejectionFunction();
}
// 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,
_beforeSendRumCallback,
_setCookieAsSecure
);
_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[i].error
);
}
_processExceptionQueue = [];
for (i = 0; i < _trackEventQueue.length; i++) {
_publicRaygunFunctions.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') {
var executedFilter = _filteredKeys[i].exec(key);
if (executedFilter !== null && executedFilter !== undefined) {
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, error) {
if (_providerState !== ProviderStates.READY) {
_processExceptionQueue.push({
stackTrace: stackTrace,
options: options,
userTriggered: userTriggered,
error: error
});
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() &&
typeof msg.substring === 'function' &&
msg.substring(0, scriptError.length) === scriptError &&
stackTrace.stack[0].url !== null &&
stackTrace.stack[0].url !== undefined &&
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;
}
var foundValidDomain = false;
for (var i = 0; !foundValidDomain && stackTrace.stack && i < stackTrace.stack.length; i++) {
if (
stackTrace.stack[i] !== null &&
stackTrace.stack[i] !== undefined &&
stackTrace.stack[i].url !== null &&
stackTrace.stack[i].url !== undefined
) {
for (var j in _whitelistedScriptDomains) {
if (stackTrace.stack[i].url.indexOf(_whitelistedScriptDomains[j]) > -1) {
foundValidDomain = true;
}
}
if (stackTrace.stack[i].url.indexOf(domain) > -1) {
foundValidDomain = true;
}
}
}
if (!foundValidDomain) {
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 if (typeof _tags === 'string') {
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;
} else if(typeof error === "string") {
finalMessage = error;
}
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, _successCallback, _errorCallback) {
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);
if (_successCallback && typeof _successCallback === 'function') {
_successCallback(xhr, url, data);
}
};
} 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);
if (_successCallback && typeof _successCallback === 'function') {
_successCallback(xhr, url, data);
}
};
}
xhr.onerror = function() {
Raygun.Utilities.log('failed to post to Raygun');
callAfterSend(this);
if (_errorCallback && typeof _errorCallback === 'function') {
_errorCallback(xhr, url, data);
}
};
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) {
window.Raygun = Raygun;
}
TraceKit.setRaygun(Raygun);
return Raygun;
};
window.__instantiatedRaygun = raygunFactory(window, window.jQuery);