@sentry/browser
Version:
Official Sentry SDK for browsers
151 lines (147 loc) • 6.1 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const browser = require('@sentry/core/browser');
const browserUtils = require('@sentry-internal/browser-utils');
const INTEGRATION_NAME = "GraphQLClient";
const _graphqlClientIntegration = ((options) => {
return {
name: INTEGRATION_NAME,
setup(client) {
_updateSpanWithGraphQLData(client, options);
_updateBreadcrumbWithGraphQLData(client, options);
}
};
});
function _updateSpanWithGraphQLData(client, options) {
client.on("beforeOutgoingRequestSpan", (span, hint) => {
const spanJSON = browser.spanToJSON(span);
const spanAttributes = spanJSON.data || {};
const spanOp = spanAttributes[browser.SEMANTIC_ATTRIBUTE_SENTRY_OP];
const isHttpClientSpan = spanOp === "http.client";
if (!isHttpClientSpan) {
return;
}
const httpUrl = spanAttributes[browser.SEMANTIC_ATTRIBUTE_URL_FULL] || spanAttributes["http.url"] || spanAttributes["url"];
const httpMethod = spanAttributes[browser.SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || spanAttributes["http.method"];
if (!browser.isString(httpUrl) || !browser.isString(httpMethod)) {
return;
}
const { endpoints } = options;
const isTracedGraphqlEndpoint = browser.stringMatchesSomePattern(httpUrl, endpoints);
const payload = getRequestPayloadXhrOrFetch(hint);
if (isTracedGraphqlEndpoint && payload) {
const graphqlBody = getGraphQLRequestPayload(payload);
if (graphqlBody) {
const operationInfo = _getGraphQLOperation(graphqlBody);
span.updateName(`${httpMethod} ${httpUrl} (${operationInfo})`);
if (isStandardRequest(graphqlBody)) {
span.setAttribute("graphql.document", graphqlBody.query);
}
if (isPersistedRequest(graphqlBody)) {
span.setAttribute("graphql.persisted_query.hash.sha256", graphqlBody.extensions.persistedQuery.sha256Hash);
span.setAttribute("graphql.persisted_query.version", graphqlBody.extensions.persistedQuery.version);
}
}
}
});
}
function _updateBreadcrumbWithGraphQLData(client, options) {
client.on("beforeOutgoingRequestBreadcrumb", (breadcrumb, handlerData) => {
const { category, type, data } = breadcrumb;
const isFetch = category === "fetch";
const isXhr = category === "xhr";
const isHttpBreadcrumb = type === "http";
if (isHttpBreadcrumb && (isFetch || isXhr)) {
const httpUrl = data?.url;
const { endpoints } = options;
const isTracedGraphqlEndpoint = browser.stringMatchesSomePattern(httpUrl, endpoints);
const payload = getRequestPayloadXhrOrFetch(handlerData);
if (isTracedGraphqlEndpoint && data && payload) {
const graphqlBody = getGraphQLRequestPayload(payload);
if (!data.graphql && graphqlBody) {
const operationInfo = _getGraphQLOperation(graphqlBody);
data["graphql.operation"] = operationInfo;
if (isStandardRequest(graphqlBody)) {
data["graphql.document"] = graphqlBody.query;
}
if (isPersistedRequest(graphqlBody)) {
data["graphql.persisted_query.hash.sha256"] = graphqlBody.extensions.persistedQuery.sha256Hash;
data["graphql.persisted_query.version"] = graphqlBody.extensions.persistedQuery.version;
}
}
}
}
});
}
function _getGraphQLOperation(requestBody) {
if (isPersistedRequest(requestBody)) {
return `persisted ${requestBody.operationName}`;
}
if (isStandardRequest(requestBody)) {
const { query: graphqlQuery, operationName: graphqlOperationName } = requestBody;
const { operationName = graphqlOperationName, operationType } = parseGraphQLQuery(graphqlQuery);
const operationInfo = operationName ? `${operationType} ${operationName}` : `${operationType}`;
return operationInfo;
}
return "unknown";
}
function getRequestPayloadXhrOrFetch(hint) {
const isXhr = "xhr" in hint;
let body;
if (isXhr) {
const sentryXhrData = hint.xhr[browserUtils.SENTRY_XHR_DATA_KEY];
body = sentryXhrData && browserUtils.getBodyString(sentryXhrData.body)[0];
} else {
const sentryFetchData = browserUtils.getFetchRequestArgBody(hint.input);
body = browserUtils.getBodyString(sentryFetchData)[0];
}
return body;
}
function parseGraphQLQuery(query) {
const namedQueryRe = /^(?:\s*)(query|mutation|subscription)(?:\s*)(\w+)(?:\s*)[{(]/;
const unnamedQueryRe = /^(?:\s*)(query|mutation|subscription)(?:\s*)[{(]/;
const namedMatch = query.match(namedQueryRe);
if (namedMatch) {
return {
operationType: namedMatch[1],
operationName: namedMatch[2]
};
}
const unnamedMatch = query.match(unnamedQueryRe);
if (unnamedMatch) {
return {
operationType: unnamedMatch[1],
operationName: void 0
};
}
return {
operationType: void 0,
operationName: void 0
};
}
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isStandardRequest(payload) {
return isObject(payload) && typeof payload.query === "string";
}
function isPersistedRequest(payload) {
return isObject(payload) && typeof payload.operationName === "string" && isObject(payload.extensions) && isObject(payload.extensions.persistedQuery) && typeof payload.extensions.persistedQuery.sha256Hash === "string" && typeof payload.extensions.persistedQuery.version === "number";
}
function getGraphQLRequestPayload(payload) {
try {
const requestBody = JSON.parse(payload);
if (isStandardRequest(requestBody) || isPersistedRequest(requestBody)) {
return requestBody;
}
return void 0;
} catch {
return void 0;
}
}
const graphqlClientIntegration = browser.defineIntegration(_graphqlClientIntegration);
exports._getGraphQLOperation = _getGraphQLOperation;
exports.getGraphQLRequestPayload = getGraphQLRequestPayload;
exports.getRequestPayloadXhrOrFetch = getRequestPayloadXhrOrFetch;
exports.graphqlClientIntegration = graphqlClientIntegration;
exports.parseGraphQLQuery = parseGraphQLQuery;
//# sourceMappingURL=graphqlClient.js.map