Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sentry-internal/tracing

Package Overview
Dependencies
Maintainers
9
Versions
123
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sentry-internal/tracing - npm Package Compare versions

Comparing version 7.107.0 to 7.108.0

cjs/browser/web-vitals/onTTFB.js

61

cjs/browser/browsertracing.js

@@ -89,6 +89,6 @@ Object.defineProperty(exports, '__esModule', { value: true });

/** Stores a mapping of interactionIds from PerformanceEventTimings to the origin interaction path */
this._interactionIdtoRouteNameMapping = {};
this._interactionIdToRouteNameMapping = {};
if (this.options.enableInp) {
index.startTrackingINP(this._interactionIdtoRouteNameMapping);
index.startTrackingINP(this._interactionIdToRouteNameMapping);
}

@@ -344,3 +344,3 @@ if (this.options.enableLongTask) {

_registerInpInteractionListener() {
instrument.addPerformanceInstrumentationHandler('event', ({ entries }) => {
const handleEntries = ({ entries }) => {
const client = core.getClient();

@@ -358,11 +358,17 @@ // We need to get the replay, user, and activeTransaction from the current scope

const user = currentScope !== undefined ? currentScope.getUser() : undefined;
for (const entry of entries) {
entries.forEach(entry => {
if (isPerformanceEventTiming(entry)) {
const interactionId = entry.interactionId;
if (interactionId === undefined) {
return;
}
const existingInteraction = this._interactionIdToRouteNameMapping[interactionId];
const duration = entry.duration;
const keys = Object.keys(this._interactionIdtoRouteNameMapping);
const startTime = entry.startTime;
const keys = Object.keys(this._interactionIdToRouteNameMapping);
const minInteractionId =
keys.length > 0
? keys.reduce((a, b) => {
return this._interactionIdtoRouteNameMapping[a].duration <
this._interactionIdtoRouteNameMapping[b].duration
return this._interactionIdToRouteNameMapping[a].duration <
this._interactionIdToRouteNameMapping[b].duration
? a

@@ -372,15 +378,35 @@ : b;

: undefined;
if (
// For a first input event to be considered, we must check that an interaction event does not already exist with the same duration and start time.
// This is also checked in the web-vitals library.
if (entry.entryType === 'first-input') {
const matchingEntry = keys
.map(key => this._interactionIdToRouteNameMapping[key])
.some(interaction => {
return interaction.duration === duration && interaction.startTime === startTime;
});
if (matchingEntry) {
return;
}
}
// Interactions with an id of 0 and are not first-input are not valid.
if (!interactionId) {
return;
}
// If the interaction already exists, we want to use the duration of the longest entry, since that is what the INP metric uses.
if (existingInteraction) {
existingInteraction.duration = Math.max(existingInteraction.duration, duration);
} else if (
keys.length < MAX_INTERACTIONS ||
minInteractionId === undefined ||
duration > this._interactionIdtoRouteNameMapping[minInteractionId].duration
duration > this._interactionIdToRouteNameMapping[minInteractionId].duration
) {
const interactionId = entry.interactionId;
// If the interaction does not exist, we want to add it to the mapping if there is space, or if the duration is longer than the shortest entry.
const routeName = this._latestRoute.name;
const parentContext = this._latestRoute.context;
if (interactionId && routeName && parentContext) {
if (minInteractionId && Object.keys(this._interactionIdtoRouteNameMapping).length >= MAX_INTERACTIONS) {
if (routeName && parentContext) {
if (minInteractionId && Object.keys(this._interactionIdToRouteNameMapping).length >= MAX_INTERACTIONS) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this._interactionIdtoRouteNameMapping[minInteractionId];
delete this._interactionIdToRouteNameMapping[minInteractionId];
}
this._interactionIdtoRouteNameMapping[interactionId] = {
this._interactionIdToRouteNameMapping[interactionId] = {
routeName,

@@ -392,2 +418,3 @@ duration,

replayId,
startTime,
};

@@ -397,4 +424,6 @@ }

}
}
});
});
};
instrument.addPerformanceInstrumentationHandler('event', handleEntries);
instrument.addPerformanceInstrumentationHandler('first-input', handleEntries);
}

@@ -401,0 +430,0 @@ }

@@ -63,5 +63,5 @@ Object.defineProperty(exports, '__esModule', { value: true });

/** Stores a mapping of interactionIds from PerformanceEventTimings to the origin interaction path */
const interactionIdtoRouteNameMapping = {};
const interactionIdToRouteNameMapping = {};
if (options.enableInp) {
index.startTrackingINP(interactionIdtoRouteNameMapping);
index.startTrackingINP(interactionIdToRouteNameMapping);
}

@@ -280,3 +280,3 @@

if (options.enableInp) {
registerInpInteractionListener(interactionIdtoRouteNameMapping, latestRoute);
registerInpInteractionListener(interactionIdToRouteNameMapping, latestRoute);
}

@@ -410,3 +410,3 @@

function registerInpInteractionListener(
interactionIdtoRouteNameMapping,
interactionIdToRouteNameMapping,
latestRoute

@@ -416,3 +416,3 @@

) {
instrument.addPerformanceInstrumentationHandler('event', ({ entries }) => {
const handleEntries = ({ entries }) => {
const client = core.getClient();

@@ -430,10 +430,16 @@ // We need to get the replay, user, and activeTransaction from the current scope

const user = currentScope !== undefined ? currentScope.getUser() : undefined;
for (const entry of entries) {
entries.forEach(entry => {
if (isPerformanceEventTiming(entry)) {
const interactionId = entry.interactionId;
if (interactionId === undefined) {
return;
}
const existingInteraction = interactionIdToRouteNameMapping[interactionId];
const duration = entry.duration;
const keys = Object.keys(interactionIdtoRouteNameMapping);
const startTime = entry.startTime;
const keys = Object.keys(interactionIdToRouteNameMapping);
const minInteractionId =
keys.length > 0
? keys.reduce((a, b) => {
return interactionIdtoRouteNameMapping[a].duration < interactionIdtoRouteNameMapping[b].duration
return interactionIdToRouteNameMapping[a].duration < interactionIdToRouteNameMapping[b].duration
? a

@@ -443,12 +449,35 @@ : b;

: undefined;
if (minInteractionId === undefined || duration > interactionIdtoRouteNameMapping[minInteractionId].duration) {
const interactionId = entry.interactionId;
// For a first input event to be considered, we must check that an interaction event does not already exist with the same duration and start time.
// This is also checked in the web-vitals library.
if (entry.entryType === 'first-input') {
const matchingEntry = keys
.map(key => interactionIdToRouteNameMapping[key])
.some(interaction => {
return interaction.duration === duration && interaction.startTime === startTime;
});
if (matchingEntry) {
return;
}
}
// Interactions with an id of 0 and are not first-input are not valid.
if (!interactionId) {
return;
}
// If the interaction already exists, we want to use the duration of the longest entry, since that is what the INP metric uses.
if (existingInteraction) {
existingInteraction.duration = Math.max(existingInteraction.duration, duration);
} else if (
keys.length < MAX_INTERACTIONS ||
minInteractionId === undefined ||
duration > interactionIdToRouteNameMapping[minInteractionId].duration
) {
// If the interaction does not exist, we want to add it to the mapping if there is space, or if the duration is longer than the shortest entry.
const routeName = latestRoute.name;
const parentContext = latestRoute.context;
if (interactionId && routeName && parentContext) {
if (minInteractionId && Object.keys(interactionIdtoRouteNameMapping).length >= MAX_INTERACTIONS) {
if (routeName && parentContext) {
if (minInteractionId && Object.keys(interactionIdToRouteNameMapping).length >= MAX_INTERACTIONS) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete interactionIdtoRouteNameMapping[minInteractionId];
delete interactionIdToRouteNameMapping[minInteractionId];
}
interactionIdtoRouteNameMapping[interactionId] = {
interactionIdToRouteNameMapping[interactionId] = {
routeName,

@@ -460,2 +489,3 @@ duration,

replayId,
startTime,
};

@@ -465,4 +495,6 @@ }

}
}
});
});
};
instrument.addPerformanceInstrumentationHandler('event', handleEntries);
instrument.addPerformanceInstrumentationHandler('first-input', handleEntries);
}

@@ -469,0 +501,0 @@

@@ -10,2 +10,3 @@ Object.defineProperty(exports, '__esModule', { value: true });

const observe = require('./web-vitals/lib/observe.js');
const onTTFB = require('./web-vitals/onTTFB.js');

@@ -18,2 +19,3 @@ const handlers = {};

let _previousLcp;
let _previousTtfb;
let _previousInp;

@@ -51,2 +53,9 @@

* Add a callback that will be triggered when a FID metric is available.
*/
function addTtfbInstrumentationHandler(callback) {
return addMetricObserver('ttfb', callback, instrumentTtfb, _previousTtfb);
}
/**
* Add a callback that will be triggered when a FID metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.

@@ -135,2 +144,11 @@ */

function instrumentTtfb() {
return onTTFB.onTTFB(metric => {
triggerHandlers('ttfb', {
metric,
});
_previousTtfb = metric;
});
}
function instrumentInp() {

@@ -219,2 +237,3 @@ return getINP.onINP(metric => {

exports.addPerformanceInstrumentationHandler = addPerformanceInstrumentationHandler;
exports.addTtfbInstrumentationHandler = addTtfbInstrumentationHandler;
//# sourceMappingURL=instrument.js.map

@@ -10,2 +10,3 @@ Object.defineProperty(exports, '__esModule', { value: true });

const utils$1 = require('./utils.js');
const getNavigationEntry = require('../web-vitals/lib/getNavigationEntry.js');

@@ -49,2 +50,3 @@ const MAX_INT_AS_BYTES = 2147483647;

const lcpCallback = _trackLCP();
const ttfbCallback = _trackTtfb();

@@ -55,2 +57,3 @@ return () => {

lcpCallback();
ttfbCallback();
};

@@ -184,6 +187,52 @@ }

function _trackTtfb() {
return instrument.addTtfbInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
debugBuild.DEBUG_BUILD && utils.logger.log('[Measurements] Adding TTFB');
_measurements['ttfb'] = { value: metric.value, unit: 'millisecond' };
});
}
const INP_ENTRY_MAP = {
click: 'click',
pointerdown: 'click',
pointerup: 'click',
mousedown: 'click',
mouseup: 'click',
touchstart: 'click',
touchend: 'click',
mouseover: 'hover',
mouseout: 'hover',
mouseenter: 'hover',
mouseleave: 'hover',
pointerover: 'hover',
pointerout: 'hover',
pointerenter: 'hover',
pointerleave: 'hover',
dragstart: 'drag',
dragend: 'drag',
drag: 'drag',
dragenter: 'drag',
dragleave: 'drag',
dragover: 'drag',
drop: 'drag',
keydown: 'press',
keyup: 'press',
keypress: 'press',
input: 'press',
};
/** Starts tracking the Interaction to Next Paint on the current page. */
function _trackINP(interactionIdtoRouteNameMapping) {
function _trackINP(interactionIdToRouteNameMapping) {
return instrument.addInpInstrumentationHandler(({ metric }) => {
const entry = metric.entries.find(e => e.name === 'click' || e.name === 'pointerdown');
if (metric.value === undefined) {
return;
}
const entry = metric.entries.find(
entry => entry.duration === metric.value && INP_ENTRY_MAP[entry.name] !== undefined,
);
const client = core.getClient();

@@ -193,2 +242,3 @@ if (!entry || !client) {

}
const interactionType = INP_ENTRY_MAP[entry.name];
const options = client.getOptions();

@@ -198,12 +248,8 @@ /** Build the INP span, create an envelope from the span, and then send the envelope */

const duration = msToSec(metric.value);
const { routeName, parentContext, activeTransaction, user, replayId } =
entry.interactionId !== undefined
? interactionIdtoRouteNameMapping[entry.interactionId]
: {
routeName: undefined,
parentContext: undefined,
activeTransaction: undefined,
user: undefined,
replayId: undefined,
};
const interaction =
entry.interactionId !== undefined ? interactionIdToRouteNameMapping[entry.interactionId] : undefined;
if (interaction === undefined) {
return;
}
const { routeName, parentContext, activeTransaction, user, replayId } = interaction;
const userDisplay = user !== undefined ? user.email || user.id || user.ip_address : undefined;

@@ -215,3 +261,3 @@ // eslint-disable-next-line deprecation/deprecation

endTimestamp: startTime + duration,
op: 'ui.interaction.click',
op: `ui.interaction.${interactionType}`,
name: utils.htmlTreeAsString(entry.target),

@@ -264,5 +310,2 @@ attributes: {

let responseStartTimestamp;
let requestStartTimestamp;
const { op, start_timestamp: transactionStartTime } = core.spanToJSON(transaction);

@@ -283,4 +326,2 @@

_addNavigationSpans(transaction, entry, timeOrigin);
responseStartTimestamp = timeOrigin + msToSec(entry.responseStart);
requestStartTimestamp = timeOrigin + msToSec(entry.requestStart);
break;

@@ -322,3 +363,3 @@ }

if (op === 'pageload') {
_addTtfbToMeasurements(_measurements, responseStartTimestamp, requestStartTimestamp, transactionStartTime);
_addTtfbRequestTimeToMeasurements(_measurements);

@@ -607,36 +648,16 @@ ['fcp', 'fp', 'lcp'].forEach(name => {

/**
* Add ttfb information to measurements
* Add ttfb request time information to measurements.
*
* Exported for tests
* ttfb information is added via vendored web vitals library.
*/
function _addTtfbToMeasurements(
_measurements,
responseStartTimestamp,
requestStartTimestamp,
transactionStartTime,
) {
// Generate TTFB (Time to First Byte), which measured as the time between the beginning of the transaction and the
// start of the response in milliseconds
if (typeof responseStartTimestamp === 'number' && transactionStartTime) {
debugBuild.DEBUG_BUILD && utils.logger.log('[Measurements] Adding TTFB');
_measurements['ttfb'] = {
// As per https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStart,
// responseStart can be 0 if the request is coming straight from the cache.
// This might lead us to calculate a negative ttfb if we don't use Math.max here.
//
// This logic is the same as what is in the web-vitals library to calculate ttfb
// https://github.com/GoogleChrome/web-vitals/blob/2301de5015e82b09925238a228a0893635854587/src/onTTFB.ts#L92
// TODO(abhi): We should use the web-vitals library instead of this custom calculation.
value: Math.max(responseStartTimestamp - transactionStartTime, 0) * 1000,
function _addTtfbRequestTimeToMeasurements(_measurements) {
const navEntry = getNavigationEntry.getNavigationEntry() ;
const { responseStart, requestStart } = navEntry;
if (requestStart <= responseStart) {
debugBuild.DEBUG_BUILD && utils.logger.log('[Measurements] Adding TTFB Request Time');
_measurements['ttfb.requestTime'] = {
value: responseStart - requestStart,
unit: 'millisecond',
};
if (typeof requestStartTimestamp === 'number' && requestStartTimestamp <= responseStartTimestamp) {
// Capture the time spent making the request and receiving the first byte of the response.
// This is the time between the start of the request and the start of the response in milliseconds.
_measurements['ttfb.requestTime'] = {
value: (responseStartTimestamp - requestStartTimestamp) * 1000,
unit: 'millisecond',
};
}
}

@@ -679,3 +700,2 @@ }

exports._addResourceSpans = _addResourceSpans;
exports._addTtfbToMeasurements = _addTtfbToMeasurements;
exports.addPerformanceEntries = addPerformanceEntries;

@@ -682,0 +702,0 @@ exports.startTrackingINP = startTrackingINP;

@@ -87,6 +87,6 @@ import { TRACING_DEFAULTS, addTracingExtensions, startIdleTransaction, getActiveTransaction, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getClient, getCurrentScope } from '@sentry/core';

/** Stores a mapping of interactionIds from PerformanceEventTimings to the origin interaction path */
this._interactionIdtoRouteNameMapping = {};
this._interactionIdToRouteNameMapping = {};
if (this.options.enableInp) {
startTrackingINP(this._interactionIdtoRouteNameMapping);
startTrackingINP(this._interactionIdToRouteNameMapping);
}

@@ -342,3 +342,3 @@ if (this.options.enableLongTask) {

_registerInpInteractionListener() {
addPerformanceInstrumentationHandler('event', ({ entries }) => {
const handleEntries = ({ entries }) => {
const client = getClient();

@@ -356,11 +356,17 @@ // We need to get the replay, user, and activeTransaction from the current scope

const user = currentScope !== undefined ? currentScope.getUser() : undefined;
for (const entry of entries) {
entries.forEach(entry => {
if (isPerformanceEventTiming(entry)) {
const interactionId = entry.interactionId;
if (interactionId === undefined) {
return;
}
const existingInteraction = this._interactionIdToRouteNameMapping[interactionId];
const duration = entry.duration;
const keys = Object.keys(this._interactionIdtoRouteNameMapping);
const startTime = entry.startTime;
const keys = Object.keys(this._interactionIdToRouteNameMapping);
const minInteractionId =
keys.length > 0
? keys.reduce((a, b) => {
return this._interactionIdtoRouteNameMapping[a].duration <
this._interactionIdtoRouteNameMapping[b].duration
return this._interactionIdToRouteNameMapping[a].duration <
this._interactionIdToRouteNameMapping[b].duration
? a

@@ -370,15 +376,35 @@ : b;

: undefined;
if (
// For a first input event to be considered, we must check that an interaction event does not already exist with the same duration and start time.
// This is also checked in the web-vitals library.
if (entry.entryType === 'first-input') {
const matchingEntry = keys
.map(key => this._interactionIdToRouteNameMapping[key])
.some(interaction => {
return interaction.duration === duration && interaction.startTime === startTime;
});
if (matchingEntry) {
return;
}
}
// Interactions with an id of 0 and are not first-input are not valid.
if (!interactionId) {
return;
}
// If the interaction already exists, we want to use the duration of the longest entry, since that is what the INP metric uses.
if (existingInteraction) {
existingInteraction.duration = Math.max(existingInteraction.duration, duration);
} else if (
keys.length < MAX_INTERACTIONS ||
minInteractionId === undefined ||
duration > this._interactionIdtoRouteNameMapping[minInteractionId].duration
duration > this._interactionIdToRouteNameMapping[minInteractionId].duration
) {
const interactionId = entry.interactionId;
// If the interaction does not exist, we want to add it to the mapping if there is space, or if the duration is longer than the shortest entry.
const routeName = this._latestRoute.name;
const parentContext = this._latestRoute.context;
if (interactionId && routeName && parentContext) {
if (minInteractionId && Object.keys(this._interactionIdtoRouteNameMapping).length >= MAX_INTERACTIONS) {
if (routeName && parentContext) {
if (minInteractionId && Object.keys(this._interactionIdToRouteNameMapping).length >= MAX_INTERACTIONS) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this._interactionIdtoRouteNameMapping[minInteractionId];
delete this._interactionIdToRouteNameMapping[minInteractionId];
}
this._interactionIdtoRouteNameMapping[interactionId] = {
this._interactionIdToRouteNameMapping[interactionId] = {
routeName,

@@ -390,2 +416,3 @@ duration,

replayId,
startTime,
};

@@ -395,4 +422,6 @@ }

}
}
});
});
};
addPerformanceInstrumentationHandler('event', handleEntries);
addPerformanceInstrumentationHandler('first-input', handleEntries);
}

@@ -399,0 +428,0 @@ }

@@ -61,5 +61,5 @@ import { TRACING_DEFAULTS, addTracingExtensions, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getCurrentHub, startIdleTransaction, getActiveTransaction, getClient, getCurrentScope } from '@sentry/core';

/** Stores a mapping of interactionIds from PerformanceEventTimings to the origin interaction path */
const interactionIdtoRouteNameMapping = {};
const interactionIdToRouteNameMapping = {};
if (options.enableInp) {
startTrackingINP(interactionIdtoRouteNameMapping);
startTrackingINP(interactionIdToRouteNameMapping);
}

@@ -278,3 +278,3 @@

if (options.enableInp) {
registerInpInteractionListener(interactionIdtoRouteNameMapping, latestRoute);
registerInpInteractionListener(interactionIdToRouteNameMapping, latestRoute);
}

@@ -408,3 +408,3 @@

function registerInpInteractionListener(
interactionIdtoRouteNameMapping,
interactionIdToRouteNameMapping,
latestRoute

@@ -414,3 +414,3 @@

) {
addPerformanceInstrumentationHandler('event', ({ entries }) => {
const handleEntries = ({ entries }) => {
const client = getClient();

@@ -428,10 +428,16 @@ // We need to get the replay, user, and activeTransaction from the current scope

const user = currentScope !== undefined ? currentScope.getUser() : undefined;
for (const entry of entries) {
entries.forEach(entry => {
if (isPerformanceEventTiming(entry)) {
const interactionId = entry.interactionId;
if (interactionId === undefined) {
return;
}
const existingInteraction = interactionIdToRouteNameMapping[interactionId];
const duration = entry.duration;
const keys = Object.keys(interactionIdtoRouteNameMapping);
const startTime = entry.startTime;
const keys = Object.keys(interactionIdToRouteNameMapping);
const minInteractionId =
keys.length > 0
? keys.reduce((a, b) => {
return interactionIdtoRouteNameMapping[a].duration < interactionIdtoRouteNameMapping[b].duration
return interactionIdToRouteNameMapping[a].duration < interactionIdToRouteNameMapping[b].duration
? a

@@ -441,12 +447,35 @@ : b;

: undefined;
if (minInteractionId === undefined || duration > interactionIdtoRouteNameMapping[minInteractionId].duration) {
const interactionId = entry.interactionId;
// For a first input event to be considered, we must check that an interaction event does not already exist with the same duration and start time.
// This is also checked in the web-vitals library.
if (entry.entryType === 'first-input') {
const matchingEntry = keys
.map(key => interactionIdToRouteNameMapping[key])
.some(interaction => {
return interaction.duration === duration && interaction.startTime === startTime;
});
if (matchingEntry) {
return;
}
}
// Interactions with an id of 0 and are not first-input are not valid.
if (!interactionId) {
return;
}
// If the interaction already exists, we want to use the duration of the longest entry, since that is what the INP metric uses.
if (existingInteraction) {
existingInteraction.duration = Math.max(existingInteraction.duration, duration);
} else if (
keys.length < MAX_INTERACTIONS ||
minInteractionId === undefined ||
duration > interactionIdToRouteNameMapping[minInteractionId].duration
) {
// If the interaction does not exist, we want to add it to the mapping if there is space, or if the duration is longer than the shortest entry.
const routeName = latestRoute.name;
const parentContext = latestRoute.context;
if (interactionId && routeName && parentContext) {
if (minInteractionId && Object.keys(interactionIdtoRouteNameMapping).length >= MAX_INTERACTIONS) {
if (routeName && parentContext) {
if (minInteractionId && Object.keys(interactionIdToRouteNameMapping).length >= MAX_INTERACTIONS) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete interactionIdtoRouteNameMapping[minInteractionId];
delete interactionIdToRouteNameMapping[minInteractionId];
}
interactionIdtoRouteNameMapping[interactionId] = {
interactionIdToRouteNameMapping[interactionId] = {
routeName,

@@ -458,2 +487,3 @@ duration,

replayId,
startTime,
};

@@ -463,4 +493,6 @@ }

}
}
});
});
};
addPerformanceInstrumentationHandler('event', handleEntries);
addPerformanceInstrumentationHandler('first-input', handleEntries);
}

@@ -467,0 +499,0 @@

@@ -8,2 +8,3 @@ import { logger, getFunctionName } from '@sentry/utils';

import { observe } from './web-vitals/lib/observe.js';
import { onTTFB } from './web-vitals/onTTFB.js';

@@ -16,2 +17,3 @@ const handlers = {};

let _previousLcp;
let _previousTtfb;
let _previousInp;

@@ -49,2 +51,9 @@

* Add a callback that will be triggered when a FID metric is available.
*/
function addTtfbInstrumentationHandler(callback) {
return addMetricObserver('ttfb', callback, instrumentTtfb, _previousTtfb);
}
/**
* Add a callback that will be triggered when a FID metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.

@@ -133,2 +142,11 @@ */

function instrumentTtfb() {
return onTTFB(metric => {
triggerHandlers('ttfb', {
metric,
});
_previousTtfb = metric;
});
}
function instrumentInp() {

@@ -212,3 +230,3 @@ return onINP(metric => {

export { addClsInstrumentationHandler, addFidInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler, addPerformanceInstrumentationHandler };
export { addClsInstrumentationHandler, addFidInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler, addPerformanceInstrumentationHandler, addTtfbInstrumentationHandler };
//# sourceMappingURL=instrument.js.map
import { getActiveTransaction, spanToJSON, setMeasurement, getClient, Span, createSpanEnvelope, hasTracingEnabled, isValidSampleRate } from '@sentry/core';
import { browserPerformanceTimeOrigin, htmlTreeAsString, getComponentName, logger, parseUrl } from '@sentry/utils';
import { DEBUG_BUILD } from '../../common/debug-build.js';
import { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addLcpInstrumentationHandler, addFidInstrumentationHandler, addInpInstrumentationHandler } from '../instrument.js';
import { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addLcpInstrumentationHandler, addFidInstrumentationHandler, addTtfbInstrumentationHandler, addInpInstrumentationHandler } from '../instrument.js';
import { WINDOW } from '../types.js';
import { getVisibilityWatcher } from '../web-vitals/lib/getVisibilityWatcher.js';
import { _startChild, isMeasurementValue } from './utils.js';
import { getNavigationEntry } from '../web-vitals/lib/getNavigationEntry.js';

@@ -46,2 +47,3 @@ const MAX_INT_AS_BYTES = 2147483647;

const lcpCallback = _trackLCP();
const ttfbCallback = _trackTtfb();

@@ -52,2 +54,3 @@ return () => {

lcpCallback();
ttfbCallback();
};

@@ -181,6 +184,52 @@ }

function _trackTtfb() {
return addTtfbInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
DEBUG_BUILD && logger.log('[Measurements] Adding TTFB');
_measurements['ttfb'] = { value: metric.value, unit: 'millisecond' };
});
}
const INP_ENTRY_MAP = {
click: 'click',
pointerdown: 'click',
pointerup: 'click',
mousedown: 'click',
mouseup: 'click',
touchstart: 'click',
touchend: 'click',
mouseover: 'hover',
mouseout: 'hover',
mouseenter: 'hover',
mouseleave: 'hover',
pointerover: 'hover',
pointerout: 'hover',
pointerenter: 'hover',
pointerleave: 'hover',
dragstart: 'drag',
dragend: 'drag',
drag: 'drag',
dragenter: 'drag',
dragleave: 'drag',
dragover: 'drag',
drop: 'drag',
keydown: 'press',
keyup: 'press',
keypress: 'press',
input: 'press',
};
/** Starts tracking the Interaction to Next Paint on the current page. */
function _trackINP(interactionIdtoRouteNameMapping) {
function _trackINP(interactionIdToRouteNameMapping) {
return addInpInstrumentationHandler(({ metric }) => {
const entry = metric.entries.find(e => e.name === 'click' || e.name === 'pointerdown');
if (metric.value === undefined) {
return;
}
const entry = metric.entries.find(
entry => entry.duration === metric.value && INP_ENTRY_MAP[entry.name] !== undefined,
);
const client = getClient();

@@ -190,2 +239,3 @@ if (!entry || !client) {

}
const interactionType = INP_ENTRY_MAP[entry.name];
const options = client.getOptions();

@@ -195,12 +245,8 @@ /** Build the INP span, create an envelope from the span, and then send the envelope */

const duration = msToSec(metric.value);
const { routeName, parentContext, activeTransaction, user, replayId } =
entry.interactionId !== undefined
? interactionIdtoRouteNameMapping[entry.interactionId]
: {
routeName: undefined,
parentContext: undefined,
activeTransaction: undefined,
user: undefined,
replayId: undefined,
};
const interaction =
entry.interactionId !== undefined ? interactionIdToRouteNameMapping[entry.interactionId] : undefined;
if (interaction === undefined) {
return;
}
const { routeName, parentContext, activeTransaction, user, replayId } = interaction;
const userDisplay = user !== undefined ? user.email || user.id || user.ip_address : undefined;

@@ -212,3 +258,3 @@ // eslint-disable-next-line deprecation/deprecation

endTimestamp: startTime + duration,
op: 'ui.interaction.click',
op: `ui.interaction.${interactionType}`,
name: htmlTreeAsString(entry.target),

@@ -261,5 +307,2 @@ attributes: {

let responseStartTimestamp;
let requestStartTimestamp;
const { op, start_timestamp: transactionStartTime } = spanToJSON(transaction);

@@ -280,4 +323,2 @@

_addNavigationSpans(transaction, entry, timeOrigin);
responseStartTimestamp = timeOrigin + msToSec(entry.responseStart);
requestStartTimestamp = timeOrigin + msToSec(entry.requestStart);
break;

@@ -319,3 +360,3 @@ }

if (op === 'pageload') {
_addTtfbToMeasurements(_measurements, responseStartTimestamp, requestStartTimestamp, transactionStartTime);
_addTtfbRequestTimeToMeasurements(_measurements);

@@ -604,36 +645,16 @@ ['fcp', 'fp', 'lcp'].forEach(name => {

/**
* Add ttfb information to measurements
* Add ttfb request time information to measurements.
*
* Exported for tests
* ttfb information is added via vendored web vitals library.
*/
function _addTtfbToMeasurements(
_measurements,
responseStartTimestamp,
requestStartTimestamp,
transactionStartTime,
) {
// Generate TTFB (Time to First Byte), which measured as the time between the beginning of the transaction and the
// start of the response in milliseconds
if (typeof responseStartTimestamp === 'number' && transactionStartTime) {
DEBUG_BUILD && logger.log('[Measurements] Adding TTFB');
_measurements['ttfb'] = {
// As per https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStart,
// responseStart can be 0 if the request is coming straight from the cache.
// This might lead us to calculate a negative ttfb if we don't use Math.max here.
//
// This logic is the same as what is in the web-vitals library to calculate ttfb
// https://github.com/GoogleChrome/web-vitals/blob/2301de5015e82b09925238a228a0893635854587/src/onTTFB.ts#L92
// TODO(abhi): We should use the web-vitals library instead of this custom calculation.
value: Math.max(responseStartTimestamp - transactionStartTime, 0) * 1000,
function _addTtfbRequestTimeToMeasurements(_measurements) {
const navEntry = getNavigationEntry() ;
const { responseStart, requestStart } = navEntry;
if (requestStart <= responseStart) {
DEBUG_BUILD && logger.log('[Measurements] Adding TTFB Request Time');
_measurements['ttfb.requestTime'] = {
value: responseStart - requestStart,
unit: 'millisecond',
};
if (typeof requestStartTimestamp === 'number' && requestStartTimestamp <= responseStartTimestamp) {
// Capture the time spent making the request and receiving the first byte of the response.
// This is the time between the start of the request and the start of the response in milliseconds.
_measurements['ttfb.requestTime'] = {
value: (responseStartTimestamp - requestStartTimestamp) * 1000,
unit: 'millisecond',
};
}
}

@@ -674,3 +695,3 @@ }

export { _addMeasureSpans, _addResourceSpans, _addTtfbToMeasurements, addPerformanceEntries, startTrackingINP, startTrackingInteractions, startTrackingLongTasks, startTrackingWebVitals };
export { _addMeasureSpans, _addResourceSpans, addPerformanceEntries, startTrackingINP, startTrackingInteractions, startTrackingLongTasks, startTrackingWebVitals };
//# sourceMappingURL=index.js.map
{
"name": "@sentry-internal/tracing",
"version": "7.107.0",
"version": "7.108.0",
"description": "Sentry Internal Tracing Package",

@@ -32,5 +32,5 @@ "repository": "git://github.com/getsentry/sentry-javascript.git",

"dependencies": {
"@sentry/core": "7.107.0",
"@sentry/types": "7.107.0",
"@sentry/utils": "7.107.0"
"@sentry/core": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
},

@@ -37,0 +37,0 @@ "devDependencies": {

@@ -128,3 +128,3 @@ import { Hub } from '@sentry/core';

private _hasSetTracePropagationTargets;
private _interactionIdtoRouteNameMapping;
private _interactionIdToRouteNameMapping;
private _latestRoute;

@@ -131,0 +131,0 @@ constructor(_options?: Partial<BrowserTracingOptions>);

@@ -6,3 +6,3 @@ export * from '../exports';

export { instrumentOutgoingRequests, defaultRequestInstrumentationOptions } from './request';
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, } from './instrument';
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, addTtfbInstrumentationHandler, addInpInstrumentationHandler, } from './instrument';
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,2 @@

type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource';
type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource' | 'first-input';
interface PerformanceEntry {

@@ -84,2 +84,8 @@ readonly duration: number;

* Add a callback that will be triggered when a FID metric is available.
*/
export declare function addTtfbInstrumentationHandler(callback: (data: {
metric: Metric;
}) => void): CleanupHandlerCallback;
/**
* Add a callback that will be triggered when a FID metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.

@@ -99,3 +105,3 @@ */

}) => void): CleanupHandlerCallback;
export declare function addPerformanceInstrumentationHandler(type: 'event', callback: (data: {
export declare function addPerformanceInstrumentationHandler(type: 'event' | 'first-input', callback: (data: {
entries: ((PerformanceEntry & {

@@ -102,0 +108,0 @@ target?: unknown | null;

import { Transaction } from '@sentry/core';
import { Measurements } from '@sentry/types';
import { InteractionRouteNameMapping } from '../web-vitals/types';

@@ -36,8 +35,2 @@ /**

export declare function _addResourceSpans(transaction: Transaction, entry: ResourceEntry, resourceUrl: string, startTime: number, duration: number, timeOrigin: number): void;
/**
* Add ttfb information to measurements
*
* Exported for tests
*/
export declare function _addTtfbToMeasurements(_measurements: Measurements, responseStartTimestamp: number | undefined, requestStartTimestamp: number | undefined, transactionStartTime: number | undefined): void;
//# sourceMappingURL=index.d.ts.map

@@ -97,4 +97,5 @@ import { Transaction, TransactionContext, User } from '@sentry/types';

replayId?: string;
startTime: number;
};
};
//# sourceMappingURL=types.d.ts.map

@@ -128,3 +128,3 @@ import type { Hub } from '@sentry/core';

private _hasSetTracePropagationTargets;
private _interactionIdtoRouteNameMapping;
private _interactionIdToRouteNameMapping;
private _latestRoute;

@@ -131,0 +131,0 @@ constructor(_options?: Partial<BrowserTracingOptions>);

@@ -6,3 +6,3 @@ export * from '../exports';

export { instrumentOutgoingRequests, defaultRequestInstrumentationOptions } from './request';
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, } from './instrument';
export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, addTtfbInstrumentationHandler, addInpInstrumentationHandler, } from './instrument';
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,2 @@

type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource';
type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource' | 'first-input';
interface PerformanceEntry {

@@ -84,2 +84,8 @@ readonly duration: number;

* Add a callback that will be triggered when a FID metric is available.
*/
export declare function addTtfbInstrumentationHandler(callback: (data: {
metric: Metric;
}) => void): CleanupHandlerCallback;
/**
* Add a callback that will be triggered when a FID metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.

@@ -99,3 +105,3 @@ */

}) => void): CleanupHandlerCallback;
export declare function addPerformanceInstrumentationHandler(type: 'event', callback: (data: {
export declare function addPerformanceInstrumentationHandler(type: 'event' | 'first-input', callback: (data: {
entries: ((PerformanceEntry & {

@@ -102,0 +108,0 @@ target?: unknown | null;

import type { Transaction } from '@sentry/core';
import type { Measurements } from '@sentry/types';
import type { InteractionRouteNameMapping } from '../web-vitals/types';

@@ -36,8 +35,2 @@ /**

export declare function _addResourceSpans(transaction: Transaction, entry: ResourceEntry, resourceUrl: string, startTime: number, duration: number, timeOrigin: number): void;
/**
* Add ttfb information to measurements
*
* Exported for tests
*/
export declare function _addTtfbToMeasurements(_measurements: Measurements, responseStartTimestamp: number | undefined, requestStartTimestamp: number | undefined, transactionStartTime: number | undefined): void;
//# sourceMappingURL=index.d.ts.map

@@ -97,4 +97,5 @@ import type { Transaction, TransactionContext, User } from '@sentry/types';

replayId?: string;
startTime: number;
};
};
//# sourceMappingURL=types.d.ts.map

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc