@sentry/browser
Version:
Official Sentry SDK for browsers
107 lines (97 loc) • 3.73 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var utils = require('@sentry/utils');
var flags = require('../flags.js');
var global = utils.getGlobalObject();
let cachedFetchImpl;
/**
* A special usecase for incorrectly wrapped Fetch APIs in conjunction with ad-blockers.
* Whenever someone wraps the Fetch API and returns the wrong promise chain,
* this chain becomes orphaned and there is no possible way to capture it's rejections
* other than allowing it bubble up to this very handler. eg.
*
* var f = window.fetch;
* window.fetch = function () {
* var p = f.apply(this, arguments);
*
* p.then(function() {
* console.log('hi.');
* });
*
* return p;
* }
*
* `p.then(function () { ... })` is producing a completely separate promise chain,
* however, what's returned is `p` - the result of original `fetch` call.
*
* This mean, that whenever we use the Fetch API to send our own requests, _and_
* some ad-blocker blocks it, this orphaned chain will _always_ reject,
* effectively causing another event to be captured.
* This makes a whole process become an infinite loop, which we need to somehow
* deal with, and break it in one way or another.
*
* To deal with this issue, we are making sure that we _always_ use the real
* browser Fetch API, instead of relying on what `window.fetch` exposes.
* The only downside to this would be missing our own requests as breadcrumbs,
* but because we are already not doing this, it should be just fine.
*
* Possible failed fetch error messages per-browser:
*
* Chrome: Failed to fetch
* Edge: Failed to Fetch
* Firefox: NetworkError when attempting to fetch resource
* Safari: resource blocked by content blocker
*/
function getNativeFetchImplementation() {
if (cachedFetchImpl) {
return cachedFetchImpl;
}
// Fast path to avoid DOM I/O
if (utils.isNativeFetch(global.fetch)) {
return (cachedFetchImpl = global.fetch.bind(global));
}
var document = global.document;
let fetchImpl = global.fetch;
if (document && typeof document.createElement === 'function') {
try {
var sandbox = document.createElement('iframe');
sandbox.hidden = true;
document.head.appendChild(sandbox);
var contentWindow = sandbox.contentWindow;
if (contentWindow && contentWindow.fetch) {
fetchImpl = contentWindow.fetch;
}
document.head.removeChild(sandbox);
} catch (e) {
flags.IS_DEBUG_BUILD &&
utils.logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e);
}
}
return (cachedFetchImpl = fetchImpl.bind(global));
/* eslint-enable @typescript-eslint/unbound-method */
}
/**
* Sends sdk client report using sendBeacon or fetch as a fallback if available
*
* @param url report endpoint
* @param body report payload
*/
function sendReport(url, body) {
var isRealNavigator = Object.prototype.toString.call(global && global.navigator) === '[object Navigator]';
var hasSendBeacon = isRealNavigator && typeof global.navigator.sendBeacon === 'function';
if (hasSendBeacon) {
// Prevent illegal invocations - https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
var sendBeacon = global.navigator.sendBeacon.bind(global.navigator);
sendBeacon(url, body);
} else if (utils.supportsFetch()) {
var fetch = getNativeFetchImplementation();
fetch(url, {
body,
method: 'POST',
credentials: 'omit',
keepalive: true,
}).then(null, error => utils.logger.error(error));
}
}
exports.getNativeFetchImplementation = getNativeFetchImplementation;
exports.sendReport = sendReport;
//# sourceMappingURL=utils.js.map