init commit

This commit is contained in:
rxliuli
2025-11-04 05:03:50 +08:00
commit bce557cc2d
1396 changed files with 172991 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { createTransport } from '@sentry/core';
import { rejectedSyncPromise } from '@sentry/utils';
import { getNativeFetchImplementation, clearCachedFetchImplementation } from './utils.js';
/**
* Creates a Transport that uses the Fetch API to send events to Sentry.
*/
function makeFetchTransport(
options,
nativeFetch = getNativeFetchImplementation(),
) {
let pendingBodySize = 0;
let pendingCount = 0;
function makeRequest(request) {
const requestSize = request.body.length;
pendingBodySize += requestSize;
pendingCount++;
const requestOptions = {
body: request.body,
method: 'POST',
referrerPolicy: 'origin',
headers: options.headers,
// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to
// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".
// The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're
// frequently sending events right before the user is switching pages (eg. whenfinishing navigation transactions).
// Gotchas:
// - `keepalive` isn't supported by Firefox
// - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch):
// If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error.
// We will therefore only activate the flag when we're below that limit.
// There is also a limit of requests that can be open at the same time, so we also limit this to 15
// See https://github.com/getsentry/sentry-javascript/pull/7553 for details
keepalive: pendingBodySize <= 60000 && pendingCount < 15,
...options.fetchOptions,
};
try {
return nativeFetch(options.url, requestOptions).then(response => {
pendingBodySize -= requestSize;
pendingCount--;
return {
statusCode: response.status,
headers: {
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
'retry-after': response.headers.get('Retry-After'),
},
};
});
} catch (e) {
clearCachedFetchImplementation();
pendingBodySize -= requestSize;
pendingCount--;
return rejectedSyncPromise(e);
}
}
return createTransport(options, makeRequest);
}
export { makeFetchTransport };
//# sourceMappingURL=fetch.js.map

View File

@@ -0,0 +1,133 @@
import { makeOfflineTransport } from '@sentry/core';
import { serializeEnvelope, parseEnvelope } from '@sentry/utils';
// 'Store', 'promisifyRequest' and 'createStore' were originally copied from the 'idb-keyval' package before being
// modified and simplified: https://github.com/jakearchibald/idb-keyval
//
// At commit: 0420a704fd6cbb4225429c536b1f61112d012fca
// Original licence:
// Copyright 2016, Jake Archibald
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
function promisifyRequest(request) {
return new Promise((resolve, reject) => {
// @ts-ignore - file size hacks
request.oncomplete = request.onsuccess = () => resolve(request.result);
// @ts-ignore - file size hacks
request.onabort = request.onerror = () => reject(request.error);
});
}
/** Create or open an IndexedDb store */
function createStore(dbName, storeName) {
const request = indexedDB.open(dbName);
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
const dbp = promisifyRequest(request);
return callback => dbp.then(db => callback(db.transaction(storeName, 'readwrite').objectStore(storeName)));
}
function keys(store) {
return promisifyRequest(store.getAllKeys() );
}
/** Insert into the store */
function insert(store, value, maxQueueSize) {
return store(store => {
return keys(store).then(keys => {
if (keys.length >= maxQueueSize) {
return;
}
// We insert with an incremented key so that the entries are popped in order
store.put(value, Math.max(...keys, 0) + 1);
return promisifyRequest(store.transaction);
});
});
}
/** Pop the oldest value from the store */
function pop(store) {
return store(store => {
return keys(store).then(keys => {
if (keys.length === 0) {
return undefined;
}
return promisifyRequest(store.get(keys[0])).then(value => {
store.delete(keys[0]);
return promisifyRequest(store.transaction).then(() => value);
});
});
});
}
function createIndexedDbStore(options) {
let store;
// Lazily create the store only when it's needed
function getStore() {
if (store == undefined) {
store = createStore(options.dbName || 'sentry-offline', options.storeName || 'queue');
}
return store;
}
return {
insert: async (env) => {
try {
const serialized = await serializeEnvelope(env, options.textEncoder);
await insert(getStore(), serialized, options.maxQueueSize || 30);
} catch (_) {
//
}
},
pop: async () => {
try {
const deserialized = await pop(getStore());
if (deserialized) {
return parseEnvelope(
deserialized,
options.textEncoder || new TextEncoder(),
options.textDecoder || new TextDecoder(),
);
}
} catch (_) {
//
}
return undefined;
},
};
}
function makeIndexedDbOfflineTransport(
createTransport,
) {
return options => createTransport({ ...options, createStore: createIndexedDbStore });
}
/**
* Creates a transport that uses IndexedDb to store events when offline.
*/
function makeBrowserOfflineTransport(
createTransport,
) {
return makeIndexedDbOfflineTransport(makeOfflineTransport(createTransport));
}
export { createStore, insert, makeBrowserOfflineTransport, pop };
//# sourceMappingURL=offline.js.map

View File

@@ -0,0 +1,85 @@
import { isNativeFetch, logger } from '@sentry/utils';
import { WINDOW } from '../helpers.js';
let cachedFetchImpl = undefined;
/**
* 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.
*
* const f = window.fetch;
* window.fetch = function () {
* const 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;
}
/* eslint-disable @typescript-eslint/unbound-method */
// Fast path to avoid DOM I/O
if (isNativeFetch(WINDOW.fetch)) {
return (cachedFetchImpl = WINDOW.fetch.bind(WINDOW));
}
const document = WINDOW.document;
let fetchImpl = WINDOW.fetch;
// eslint-disable-next-line deprecation/deprecation
if (document && typeof document.createElement === 'function') {
try {
const sandbox = document.createElement('iframe');
sandbox.hidden = true;
document.head.appendChild(sandbox);
const contentWindow = sandbox.contentWindow;
if (contentWindow && contentWindow.fetch) {
fetchImpl = contentWindow.fetch;
}
document.head.removeChild(sandbox);
} catch (e) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e);
}
}
return (cachedFetchImpl = fetchImpl.bind(WINDOW));
/* eslint-enable @typescript-eslint/unbound-method */
}
/** Clears cached fetch impl */
function clearCachedFetchImplementation() {
cachedFetchImpl = undefined;
}
export { clearCachedFetchImplementation, getNativeFetchImplementation };
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1,52 @@
import { createTransport } from '@sentry/core';
import { SyncPromise } from '@sentry/utils';
/**
* The DONE ready state for XmlHttpRequest
*
* Defining it here as a constant b/c XMLHttpRequest.DONE is not always defined
* (e.g. during testing, it is `undefined`)
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState}
*/
const XHR_READYSTATE_DONE = 4;
/**
* Creates a Transport that uses the XMLHttpRequest API to send events to Sentry.
*/
function makeXHRTransport(options) {
function makeRequest(request) {
return new SyncPromise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onerror = reject;
xhr.onreadystatechange = () => {
if (xhr.readyState === XHR_READYSTATE_DONE) {
resolve({
statusCode: xhr.status,
headers: {
'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'),
'retry-after': xhr.getResponseHeader('Retry-After'),
},
});
}
};
xhr.open('POST', options.url);
for (const header in options.headers) {
if (Object.prototype.hasOwnProperty.call(options.headers, header)) {
xhr.setRequestHeader(header, options.headers[header]);
}
}
xhr.send(request.body);
});
}
return createTransport(options, makeRequest);
}
export { makeXHRTransport };
//# sourceMappingURL=xhr.js.map