this repo has no description
1import { getCurrentHub } from '@sentry/core';
2import { addInstrumentationHandler, getEventDescription, severityLevelFromString, safeJoin, SENTRY_XHR_DATA_KEY, parseUrl, logger, htmlTreeAsString } from '@sentry/utils';
3import { WINDOW } from '../helpers.js';
4
5/* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
7/** maxStringLength gets capped to prevent 100 breadcrumbs exceeding 1MB event payload size */
8const MAX_ALLOWED_STRING_LENGTH = 1024;
9
10const BREADCRUMB_INTEGRATION_ID = 'Breadcrumbs';
11
12/**
13 * Default Breadcrumbs instrumentations
14 * TODO: Deprecated - with v6, this will be renamed to `Instrument`
15 */
16class Breadcrumbs {
17 /**
18 * @inheritDoc
19 */
20 static __initStatic() {this.id = BREADCRUMB_INTEGRATION_ID;}
21
22 /**
23 * @inheritDoc
24 */
25 __init() {this.name = Breadcrumbs.id;}
26
27 /**
28 * Options of the breadcrumbs integration.
29 */
30 // This field is public, because we use it in the browser client to check if the `sentry` option is enabled.
31
32 /**
33 * @inheritDoc
34 */
35 constructor(options) {Breadcrumbs.prototype.__init.call(this);
36 this.options = {
37 console: true,
38 dom: true,
39 fetch: true,
40 history: true,
41 sentry: true,
42 xhr: true,
43 ...options,
44 };
45 }
46
47 /**
48 * Instrument browser built-ins w/ breadcrumb capturing
49 * - Console API
50 * - DOM API (click/typing)
51 * - XMLHttpRequest API
52 * - Fetch API
53 * - History API
54 */
55 setupOnce() {
56 if (this.options.console) {
57 addInstrumentationHandler('console', _consoleBreadcrumb);
58 }
59 if (this.options.dom) {
60 addInstrumentationHandler('dom', _domBreadcrumb(this.options.dom));
61 }
62 if (this.options.xhr) {
63 addInstrumentationHandler('xhr', _xhrBreadcrumb);
64 }
65 if (this.options.fetch) {
66 addInstrumentationHandler('fetch', _fetchBreadcrumb);
67 }
68 if (this.options.history) {
69 addInstrumentationHandler('history', _historyBreadcrumb);
70 }
71 }
72
73 /**
74 * Adds a breadcrumb for Sentry events or transactions if this option is enabled.
75 */
76 addSentryBreadcrumb(event) {
77 if (this.options.sentry) {
78 getCurrentHub().addBreadcrumb(
79 {
80 category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`,
81 event_id: event.event_id,
82 level: event.level,
83 message: getEventDescription(event),
84 },
85 {
86 event,
87 },
88 );
89 }
90 }
91} Breadcrumbs.__initStatic();
92
93/**
94 * A HOC that creaes a function that creates breadcrumbs from DOM API calls.
95 * This is a HOC so that we get access to dom options in the closure.
96 */
97function _domBreadcrumb(dom) {
98 function _innerDomBreadcrumb(handlerData) {
99 let target;
100 let keyAttrs = typeof dom === 'object' ? dom.serializeAttribute : undefined;
101
102 let maxStringLength =
103 typeof dom === 'object' && typeof dom.maxStringLength === 'number' ? dom.maxStringLength : undefined;
104 if (maxStringLength && maxStringLength > MAX_ALLOWED_STRING_LENGTH) {
105 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
106 logger.warn(
107 `\`dom.maxStringLength\` cannot exceed ${MAX_ALLOWED_STRING_LENGTH}, but a value of ${maxStringLength} was configured. Sentry will use ${MAX_ALLOWED_STRING_LENGTH} instead.`,
108 );
109 maxStringLength = MAX_ALLOWED_STRING_LENGTH;
110 }
111
112 if (typeof keyAttrs === 'string') {
113 keyAttrs = [keyAttrs];
114 }
115
116 // Accessing event.target can throw (see getsentry/raven-js#838, #768)
117 try {
118 const event = handlerData.event ;
119 target = _isEvent(event)
120 ? htmlTreeAsString(event.target, { keyAttrs, maxStringLength })
121 : htmlTreeAsString(event, { keyAttrs, maxStringLength });
122 } catch (e) {
123 target = '<unknown>';
124 }
125
126 if (target.length === 0) {
127 return;
128 }
129
130 getCurrentHub().addBreadcrumb(
131 {
132 category: `ui.${handlerData.name}`,
133 message: target,
134 },
135 {
136 event: handlerData.event,
137 name: handlerData.name,
138 global: handlerData.global,
139 },
140 );
141 }
142
143 return _innerDomBreadcrumb;
144}
145
146/**
147 * Creates breadcrumbs from console API calls
148 */
149function _consoleBreadcrumb(handlerData) {
150 // This is a hack to fix a Vue3-specific bug that causes an infinite loop of
151 // console warnings. This happens when a Vue template is rendered with
152 // an undeclared variable, which we try to stringify, ultimately causing
153 // Vue to issue another warning which repeats indefinitely.
154 // see: https://github.com/getsentry/sentry-javascript/pull/6010
155 // see: https://github.com/getsentry/sentry-javascript/issues/5916
156 for (let i = 0; i < handlerData.args.length; i++) {
157 if (handlerData.args[i] === 'ref=Ref<') {
158 handlerData.args[i + 1] = 'viewRef';
159 break;
160 }
161 }
162 const breadcrumb = {
163 category: 'console',
164 data: {
165 arguments: handlerData.args,
166 logger: 'console',
167 },
168 level: severityLevelFromString(handlerData.level),
169 message: safeJoin(handlerData.args, ' '),
170 };
171
172 if (handlerData.level === 'assert') {
173 if (handlerData.args[0] === false) {
174 breadcrumb.message = `Assertion failed: ${safeJoin(handlerData.args.slice(1), ' ') || 'console.assert'}`;
175 breadcrumb.data.arguments = handlerData.args.slice(1);
176 } else {
177 // Don't capture a breadcrumb for passed assertions
178 return;
179 }
180 }
181
182 getCurrentHub().addBreadcrumb(breadcrumb, {
183 input: handlerData.args,
184 level: handlerData.level,
185 });
186}
187
188/**
189 * Creates breadcrumbs from XHR API calls
190 */
191function _xhrBreadcrumb(handlerData) {
192 const { startTimestamp, endTimestamp } = handlerData;
193
194 const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY];
195
196 // We only capture complete, non-sentry requests
197 if (!startTimestamp || !endTimestamp || !sentryXhrData) {
198 return;
199 }
200
201 const { method, url, status_code, body } = sentryXhrData;
202
203 const data = {
204 method,
205 url,
206 status_code,
207 };
208
209 const hint = {
210 xhr: handlerData.xhr,
211 input: body,
212 startTimestamp,
213 endTimestamp,
214 };
215
216 getCurrentHub().addBreadcrumb(
217 {
218 category: 'xhr',
219 data,
220 type: 'http',
221 },
222 hint,
223 );
224}
225
226/**
227 * Creates breadcrumbs from fetch API calls
228 */
229function _fetchBreadcrumb(handlerData) {
230 const { startTimestamp, endTimestamp } = handlerData;
231
232 // We only capture complete fetch requests
233 if (!endTimestamp) {
234 return;
235 }
236
237 if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === 'POST') {
238 // We will not create breadcrumbs for fetch requests that contain `sentry_key` (internal sentry requests)
239 return;
240 }
241
242 if (handlerData.error) {
243 const data = handlerData.fetchData;
244 const hint = {
245 data: handlerData.error,
246 input: handlerData.args,
247 startTimestamp,
248 endTimestamp,
249 };
250
251 getCurrentHub().addBreadcrumb(
252 {
253 category: 'fetch',
254 data,
255 level: 'error',
256 type: 'http',
257 },
258 hint,
259 );
260 } else {
261 const data = {
262 ...handlerData.fetchData,
263 status_code: handlerData.response && handlerData.response.status,
264 };
265 const hint = {
266 input: handlerData.args,
267 response: handlerData.response,
268 startTimestamp,
269 endTimestamp,
270 };
271 getCurrentHub().addBreadcrumb(
272 {
273 category: 'fetch',
274 data,
275 type: 'http',
276 },
277 hint,
278 );
279 }
280}
281
282/**
283 * Creates breadcrumbs from history API calls
284 */
285function _historyBreadcrumb(handlerData) {
286 let from = handlerData.from;
287 let to = handlerData.to;
288 const parsedLoc = parseUrl(WINDOW.location.href);
289 let parsedFrom = parseUrl(from);
290 const parsedTo = parseUrl(to);
291
292 // Initial pushState doesn't provide `from` information
293 if (!parsedFrom.path) {
294 parsedFrom = parsedLoc;
295 }
296
297 // Use only the path component of the URL if the URL matches the current
298 // document (almost all the time when using pushState)
299 if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) {
300 to = parsedTo.relative;
301 }
302 if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) {
303 from = parsedFrom.relative;
304 }
305
306 getCurrentHub().addBreadcrumb({
307 category: 'navigation',
308 data: {
309 from,
310 to,
311 },
312 });
313}
314
315function _isEvent(event) {
316 return !!event && !!(event ).target;
317}
318
319export { BREADCRUMB_INTEGRATION_ID, Breadcrumbs };
320//# sourceMappingURL=breadcrumbs.js.map