tracker, ui, backend: checking support for network timings

This commit is contained in:
nick-delirium 2025-04-04 16:53:34 +02:00 committed by Delirium
parent b1b21937ed
commit e92ba42d82
23 changed files with 278 additions and 176 deletions

View file

@ -1466,7 +1466,7 @@ func (msg *SetNodeAttributeDict) TypeID() int {
return 52
}
type ResourceTimingDeprecated struct {
type ResourceTimingDeprecatedDeprecated struct {
message
Timestamp uint64
Duration uint64
@ -1478,7 +1478,7 @@ type ResourceTimingDeprecated struct {
Initiator string
}
func (msg *ResourceTimingDeprecated) Encode() []byte {
func (msg *ResourceTimingDeprecatedDeprecated) Encode() []byte {
buf := make([]byte, 81+len(msg.URL)+len(msg.Initiator))
buf[0] = 53
p := 1
@ -1493,11 +1493,11 @@ func (msg *ResourceTimingDeprecated) Encode() []byte {
return buf[:p]
}
func (msg *ResourceTimingDeprecated) Decode() Message {
func (msg *ResourceTimingDeprecatedDeprecated) Decode() Message {
return msg
}
func (msg *ResourceTimingDeprecated) TypeID() int {
func (msg *ResourceTimingDeprecatedDeprecated) TypeID() int {
return 53
}
@ -2424,7 +2424,7 @@ func (msg *UnbindNodes) TypeID() int {
return 115
}
type ResourceTiming struct {
type ResourceTimingDeprecated struct {
message
Timestamp uint64
Duration uint64
@ -2438,7 +2438,7 @@ type ResourceTiming struct {
Cached bool
}
func (msg *ResourceTiming) Encode() []byte {
func (msg *ResourceTimingDeprecated) Encode() []byte {
buf := make([]byte, 101+len(msg.URL)+len(msg.Initiator))
buf[0] = 116
p := 1
@ -2455,11 +2455,11 @@ func (msg *ResourceTiming) Encode() []byte {
return buf[:p]
}
func (msg *ResourceTiming) Decode() Message {
func (msg *ResourceTimingDeprecated) Decode() Message {
return msg
}
func (msg *ResourceTiming) TypeID() int {
func (msg *ResourceTimingDeprecated) TypeID() int {
return 116
}

View file

@ -873,9 +873,9 @@ func DecodeSetNodeAttributeDict(reader BytesReader) (Message, error) {
return msg, err
}
func DecodeResourceTimingDeprecated(reader BytesReader) (Message, error) {
func DecodeResourceTimingDeprecatedDeprecated(reader BytesReader) (Message, error) {
var err error = nil
msg := &ResourceTimingDeprecated{}
msg := &ResourceTimingDeprecatedDeprecated{}
if msg.Timestamp, err = reader.ReadUint(); err != nil {
return nil, err
}
@ -1500,9 +1500,9 @@ func DecodeUnbindNodes(reader BytesReader) (Message, error) {
return msg, err
}
func DecodeResourceTiming(reader BytesReader) (Message, error) {
func DecodeResourceTimingDeprecated(reader BytesReader) (Message, error) {
var err error = nil
msg := &ResourceTiming{}
msg := &ResourceTimingDeprecated{}
if msg.Timestamp, err = reader.ReadUint(); err != nil {
return nil, err
}
@ -2211,7 +2211,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
case 52:
return DecodeSetNodeAttributeDict(reader)
case 53:
return DecodeResourceTimingDeprecated(reader)
return DecodeResourceTimingDeprecatedDeprecated(reader)
case 54:
return DecodeConnectionInformation(reader)
case 55:
@ -2283,7 +2283,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
case 115:
return DecodeUnbindNodes(reader)
case 116:
return DecodeResourceTiming(reader)
return DecodeResourceTimingDeprecated(reader)
case 117:
return DecodeTabChange(reader)
case 118:

View file

@ -500,7 +500,7 @@ class SetNodeAttributeDict(Message):
self.value = value
class ResourceTimingDeprecated(Message):
class ResourceTimingDeprecatedDeprecated(Message):
__id__ = 53
def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator):
@ -853,7 +853,7 @@ class UnbindNodes(Message):
self.total_removed_percent = total_removed_percent
class ResourceTiming(Message):
class ResourceTimingDeprecated(Message):
__id__ = 116
def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator, transferred_size, cached):
@ -1177,5 +1177,3 @@ class MobileIssueEvent(Message):
self.context_string = context_string
self.context = context
self.payload = payload

View file

@ -743,7 +743,7 @@ cdef class SetNodeAttributeDict(PyMessage):
self.value = value
cdef class ResourceTimingDeprecated(PyMessage):
cdef class ResourceTimingDeprecatedDeprecated(PyMessage):
cdef public int __id__
cdef public unsigned long timestamp
cdef public unsigned long duration
@ -1269,7 +1269,7 @@ cdef class UnbindNodes(PyMessage):
self.total_removed_percent = total_removed_percent
cdef class ResourceTiming(PyMessage):
cdef class ResourceTimingDeprecated(PyMessage):
cdef public int __id__
cdef public unsigned long timestamp
cdef public unsigned long duration
@ -1764,5 +1764,3 @@ cdef class MobileIssueEvent(PyMessage):
self.context_string = context_string
self.context = context
self.payload = payload

View file

@ -486,7 +486,7 @@ class MessageCodec(Codec):
)
if message_id == 53:
return ResourceTimingDeprecated(
return ResourceTimingDeprecatedDeprecated(
timestamp=self.read_uint(reader),
duration=self.read_uint(reader),
ttfb=self.read_uint(reader),
@ -767,7 +767,7 @@ class MessageCodec(Codec):
)
if message_id == 116:
return ResourceTiming(
return ResourceTimingDeprecated(
timestamp=self.read_uint(reader),
duration=self.read_uint(reader),
ttfb=self.read_uint(reader),
@ -1029,4 +1029,3 @@ class MessageCodec(Codec):
context=self.read_string(reader),
payload=self.read_string(reader)
)

View file

@ -584,7 +584,7 @@ cdef class MessageCodec:
)
if message_id == 53:
return ResourceTimingDeprecated(
return ResourceTimingDeprecatedDeprecated(
timestamp=self.read_uint(reader),
duration=self.read_uint(reader),
ttfb=self.read_uint(reader),
@ -865,7 +865,7 @@ cdef class MessageCodec:
)
if message_id == 116:
return ResourceTiming(
return ResourceTimingDeprecated(
timestamp=self.read_uint(reader),
duration=self.read_uint(reader),
ttfb=self.read_uint(reader),
@ -1127,4 +1127,3 @@ cdef class MessageCodec:
context=self.read_string(reader),
payload=self.read_string(reader)
)

View file

@ -1,5 +1,5 @@
/* eslint-disable i18next/no-literal-string */
import { ResourceType, Timed } from 'Player';
import { IResourceRequest, ResourceType, Timed } from 'Player';
import { WsChannel } from 'Player/web/messages';
import MobilePlayer from 'Player/mobile/IOSPlayer';
import WebPlayer from 'Player/web/WebPlayer';
@ -400,8 +400,8 @@ export const NetworkPanelComp = observer(
transferredSize: 0,
});
const originalListRef = useRef([]);
const socketListRef = useRef([]);
const originalListRef = useRef<IResourceRequest[]>([]);
const socketListRef = useRef<any[]>([]);
const {
sessionStore: { devTools },
@ -433,18 +433,38 @@ export const NetworkPanelComp = observer(
// Heaviest operation here, will create a final merged network list
const processData = async () => {
const fetchUrls = new Set(
fetchList.map((ft) => {
return `${ft.name}-${Math.floor(ft.time / 100)}-${Math.floor(ft.duration / 100)}`;
}),
);
const fetchUrlMap: Record<string, number[]> = {}
const len = fetchList.length;
for (let i = 0; i < len; i++) {
const ft = fetchList[i] as any;
const key = `${ft.name}-${Math.round(ft.time / 10)}-${Math.round(ft.duration / 10)}`
if (fetchUrlMap[key]) {
fetchUrlMap[key].push(i);
}
fetchUrlMap[key] = [i];
}
// We want to get resources that aren't in fetch list
const filteredResources = await processInChunks(resourceList, (chunk) =>
chunk.filter((res: any) => {
const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`;
return !fetchUrls.has(key);
}),
const filteredResources = await processInChunks(resourceList, (chunk) => {
const clearChunk = [];
for (const res of chunk) {
const key = `${res.name}-${Math.floor(res.time / 10)}-${Math.floor(res.duration / 10)}`;
const possibleRequests = fetchUrlMap[key]
if (possibleRequests && possibleRequests.length) {
for (const i of possibleRequests) {
fetchList[i].timings = res.timings;
}
fetchUrlMap[key] = [];
} else {
clearChunk.push(res);
}
}
return clearChunk;
},
// chunk.filter((res: any) => {
// const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`;
// return !fetchUrls.has(key);
// }),
BATCH_SIZE,
25,
);
@ -612,6 +632,7 @@ export const NetworkPanelComp = observer(
return setSelectedWsChannel(socketMsgList);
}
setIsDetailsModalActive(true);
showModal(
<FetchDetailsModal

View file

@ -75,7 +75,7 @@ function FetchDetailsModal(props: Props) {
}
/>
{isXHR && <FetchTabs isSpot={isSpot} resource={resource} />}
<FetchTabs isSpot={isSpot} resource={resource} isXHR={isXHR} />
{rows && rows.length > 0 && (
<div className="flex justify-between absolute bottom-0 left-0 right-0 p-3 border-t bg-white">

View file

@ -9,10 +9,17 @@ import { TFunction } from 'i18next';
const HEADERS = 'HEADERS';
const REQUEST = 'REQUEST';
const RESPONSE = 'RESPONSE';
const TABS = [HEADERS, REQUEST, RESPONSE].map((tab) => ({
const TIMINGS = 'TIMINGS';
const TABS = [HEADERS, REQUEST, RESPONSE, TIMINGS].map((tab) => ({
text: tab,
key: tab,
}));
const RESOURCE_TABS = [
{
text: TIMINGS,
key: TIMINGS,
},
];
type RequestResponse = {
headers?: Record<string, string>;
@ -76,10 +83,11 @@ function parseRequestResponse(
interface Props {
resource: { request: string; response: string };
isSpot?: boolean;
isXHR?: boolean;
}
function FetchTabs({ resource, isSpot }: Props) {
function FetchTabs({ resource, isSpot, isXHR }: Props) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState(HEADERS);
const [activeTab, setActiveTab] = useState(isXHR ? HEADERS : TIMINGS);
const onTabClick = (tab: string) => setActiveTab(tab);
const [jsonRequest, setJsonRequest] = useState<object | null>(null);
const [jsonResponse, setJsonResponse] = useState<object | null>(null);
@ -122,10 +130,13 @@ function FetchTabs({ resource, isSpot }: Props) {
<AnimatedSVG name={ICONS.NO_RESULTS} size={30} />
<div className="mt-6 text-base font-normal">
{t('Body is empty or not captured.')}{' '}
<a href="https://docs.openreplay.com/en/sdk/network-options" className="link" target="_blank">
<a
href="https://docs.openreplay.com/en/sdk/network-options"
className="link"
target="_blank"
>
{t('Configure')}
</a>
{' '}
</a>{' '}
{t(
'network capturing to get more out of Fetch/XHR requests.',
)}
@ -160,10 +171,13 @@ function FetchTabs({ resource, isSpot }: Props) {
<AnimatedSVG name={ICONS.NO_RESULTS} size={30} />
<div className="mt-6 text-base font-normal">
{t('Body is empty or not captured.')}{' '}
<a href="https://docs.openreplay.com/en/sdk/network-options" className="link" target="_blank">
<a
href="https://docs.openreplay.com/en/sdk/network-options"
className="link"
target="_blank"
>
{t('Configure')}
</a>
{' '}
</a>{' '}
{t(
'network capturing to get more out of Fetch/XHR requests.',
)}
@ -197,11 +211,16 @@ function FetchTabs({ resource, isSpot }: Props) {
responseHeaders={responseHeaders}
/>
);
case TIMINGS:
return <div>
{resource.timings ? JSON.stringify(resource.timings, null, 2) : 'notihng :('}
</div>;
}
};
const usedTabs = isXHR ? TABS : RESOURCE_TABS;
return (
<div>
<Tabs tabs={TABS} active={activeTab} onClick={onTabClick} border />
<Tabs tabs={usedTabs} active={activeTab} onClick={onTabClick} border />
<div style={{ height: 'calc(100vh - 364px)', overflowY: 'auto' }}>
{renderActiveTab()}
</div>

View file

@ -304,6 +304,7 @@ export default class TabSessionManager {
Log(msg),
);
break;
case MType.ResourceTimingDeprecatedDeprecated:
case MType.ResourceTimingDeprecated:
case MType.ResourceTiming:
// TODO: merge `resource` and `fetch` lists into one here instead of UI

View file

@ -449,7 +449,7 @@ export default class RawMessageReader extends PrimitiveReader {
const url = this.readString(); if (url === null) { return resetPointer() }
const initiator = this.readString(); if (initiator === null) { return resetPointer() }
return {
tp: MType.ResourceTimingDeprecated,
tp: MType.ResourceTimingDeprecatedDeprecated,
timestamp,
duration,
ttfb,
@ -783,7 +783,7 @@ export default class RawMessageReader extends PrimitiveReader {
const transferredSize = this.readUint(); if (transferredSize === null) { return resetPointer() }
const cached = this.readBoolean(); if (cached === null) { return resetPointer() }
return {
tp: MType.ResourceTiming,
tp: MType.ResourceTimingDeprecated,
timestamp,
duration,
ttfb,

View file

@ -40,7 +40,7 @@ import type {
RawSetNodeAttributeDictDeprecated,
RawStringDict,
RawSetNodeAttributeDict,
RawResourceTimingDeprecated,
RawResourceTimingDeprecatedDeprecated,
RawConnectionInformation,
RawSetPageVisibility,
RawLoadFontFace,
@ -65,7 +65,7 @@ import type {
RawLongAnimationTask,
RawSelectionChange,
RawMouseThrashing,
RawResourceTiming,
RawResourceTimingDeprecated,
RawTabChange,
RawTabData,
RawCanvasNode,
@ -245,4 +245,3 @@ export type MobileNetworkCall = RawMobileNetworkCall & Timed
export type MobileSwipeEvent = RawMobileSwipeEvent & Timed
export type MobileIssueEvent = RawMobileIssueEvent & Timed

View file

@ -38,7 +38,7 @@ export const enum MType {
SetNodeAttributeDictDeprecated = 51,
StringDict = 43,
SetNodeAttributeDict = 52,
ResourceTimingDeprecated = 53,
ResourceTimingDeprecatedDeprecated = 53,
ConnectionInformation = 54,
SetPageVisibility = 55,
LoadFontFace = 57,
@ -63,7 +63,7 @@ export const enum MType {
LongAnimationTask = 89,
SelectionChange = 113,
MouseThrashing = 114,
ResourceTiming = 116,
ResourceTimingDeprecated = 116,
TabChange = 117,
TabData = 118,
CanvasNode = 119,

View file

@ -39,7 +39,7 @@ export const TP_MAP = {
51: MType.SetNodeAttributeDictDeprecated,
43: MType.StringDict,
52: MType.SetNodeAttributeDict,
53: MType.ResourceTimingDeprecated,
53: MType.ResourceTimingDeprecatedDeprecated,
54: MType.ConnectionInformation,
55: MType.SetPageVisibility,
57: MType.LoadFontFace,
@ -64,7 +64,7 @@ export const TP_MAP = {
89: MType.LongAnimationTask,
113: MType.SelectionChange,
114: MType.MouseThrashing,
116: MType.ResourceTiming,
116: MType.ResourceTimingDeprecated,
117: MType.TabChange,
118: MType.TabData,
119: MType.CanvasNode,

View file

@ -298,7 +298,7 @@ type TrSetNodeAttributeDict = [
value: string,
]
type TrResourceTimingDeprecated = [
type TrResourceTimingDeprecatedDeprecated = [
type: 53,
timestamp: number,
duration: number,
@ -526,7 +526,7 @@ type TrUnbindNodes = [
totalRemovedPercent: number,
]
type TrResourceTiming = [
type TrResourceTimingDeprecated = [
type: 116,
timestamp: number,
duration: number,
@ -910,7 +910,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
case 53: {
return {
tp: MType.ResourceTimingDeprecated,
tp: MType.ResourceTimingDeprecatedDeprecated,
timestamp: tMsg[1],
duration: tMsg[2],
ttfb: tMsg[3],
@ -1134,7 +1134,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
case 116: {
return {
tp: MType.ResourceTiming,
tp: MType.ResourceTimingDeprecated,
timestamp: tMsg[1],
duration: tMsg[2],
ttfb: tMsg[3],

View file

@ -65,7 +65,7 @@ export function getResourceName(url: string) {
return url
.split('/')
.filter((s) => s !== '')
.pop();
.pop() as string;
}
interface IResource {
@ -96,6 +96,15 @@ export interface IResourceTiming extends IResource {
success: boolean;
status: '2xx-3xx' | '4xx-5xx';
time: number;
timings: {
queueing: number;
dnsLookup: number;
initialConnection: number;
ssl: number;
ttfb: number;
contentDownload: number;
total: number;
};
}
export interface IResourceRequest extends IResource {
@ -110,26 +119,31 @@ export interface IResourceRequest extends IResource {
decodedBodySize?: number;
}
const getGraphqlReqName = (resource: IResource) => {
const getGraphqlReqName = (
resource: Partial<IResourceRequest | IResourceTiming>,
) => {
try {
if (!resource.request) return getResourceName(resource.url);
if (!resource.request) return getResourceName(resource.url ?? '');
const req = JSON.parse(resource.request);
const body = JSON.parse(req.body);
return /query (\w+)/.exec(body.query)?.[1];
return /query (\w+)/.exec(body.query)?.[1] as string;
} catch (e) {
return getResourceName(resource.url);
return getResourceName(resource.url ?? '');
}
};
export const Resource = (resource: IResource) => {
export const Resource = (
resource: Partial<IResourceRequest | IResourceTiming>,
) => {
const name =
resource.type === 'graphql'
? getGraphqlReqName(resource)
: getResourceName(resource.url);
: getResourceName(resource.url ?? '');
return {
...resource,
name,
isRed: !resource.success || resource.error, // || resource.score >= RED_BOUND,
isRed: Boolean(!resource.success || resource.error), // || resource.score >= RED_BOUND,
isYellow: false, // resource.score < RED_BOUND && resource.score >= YELLOW_BOUND,
};
};
@ -153,6 +167,15 @@ export function getResourceFromResourceTiming(
success: !failed,
status: !failed ? '2xx-3xx' : '4xx-5xx',
time: Math.max(0, msg.timestamp - sessStart),
timings: {
queueing: msg.queueing,
dnsLookup: msg.dnsLookup,
initialConnection: msg.initialConnection,
ssl: msg.ssl,
ttfb: msg.ttfb,
contentDownload: msg.contentDownload,
total: msg.total,
},
});
}
@ -169,5 +192,6 @@ export function getResourceFromNetworkRequest(
time: Math.max(0, msg.timestamp - sessStart),
decodedBodySize:
'transferredBodySize' in msg ? msg.transferredBodySize : undefined,
timings: {},
});
}

View file

@ -309,7 +309,9 @@ message 52, 'SetNodeAttributeDict' do
string 'Name'
string 'Value'
end
message 53, 'ResourceTimingDeprecated', :replayer => :devtools do
# deprecated during 1.16.0 release
message 53, 'ResourceTimingDeprecatedDeprecated', :replayer => :devtools do
uint 'Timestamp'
uint 'Duration'
uint 'TTFB'
@ -520,6 +522,25 @@ message 84, 'WSChannel', :replayer => :devtools do
string 'MessageType'
end
message 85, 'ResourceTiming', :replayer => :devtools do
uint 'Timestamp'
uint 'Duration'
uint 'TTFB'
uint 'HeaderSize'
uint 'EncodedBodySize'
uint 'DecodedBodySize'
string 'URL'
string 'Initiator'
uint 'TransferredSize'
boolean 'Cached'
uint 'Queueing'
uint 'DnsLookup'
uint 'InitialConnection'
uint 'SSL'
uint 'ContentDownload'
uint 'Total'
end
message 89, 'LongAnimationTask', :replayer => :devtools do
string 'Name'
int 'Duration'
@ -554,7 +575,8 @@ message 115, 'UnbindNodes', :replayer => false do
uint 'TotalRemovedPercent'
end
message 116, 'ResourceTiming', :replayer => :devtools do
#deprecated during 1.23.0 release
message 116, 'ResourceTimingDeprecated', :replayer => :devtools do
uint 'Timestamp'
uint 'Duration'
uint 'TTFB'

View file

@ -45,7 +45,7 @@ export declare const enum Type {
SetNodeAttributeDictDeprecated = 51,
StringDict = 43,
SetNodeAttributeDict = 52,
ResourceTimingDeprecated = 53,
ResourceTimingDeprecatedDeprecated = 53,
ConnectionInformation = 54,
SetPageVisibility = 55,
LoadFontFace = 57,
@ -75,7 +75,7 @@ export declare const enum Type {
SelectionChange = 113,
MouseThrashing = 114,
UnbindNodes = 115,
ResourceTiming = 116,
ResourceTimingDeprecated = 116,
TabChange = 117,
TabData = 118,
CanvasNode = 119,
@ -380,8 +380,8 @@ export type SetNodeAttributeDict = [
/*value:*/ string,
]
export type ResourceTimingDeprecated = [
/*type:*/ Type.ResourceTimingDeprecated,
export type ResourceTimingDeprecatedDeprecated = [
/*type:*/ Type.ResourceTimingDeprecatedDeprecated,
/*timestamp:*/ number,
/*duration:*/ number,
/*ttfb:*/ number,
@ -608,8 +608,8 @@ export type UnbindNodes = [
/*totalRemovedPercent:*/ number,
]
export type ResourceTiming = [
/*type:*/ Type.ResourceTiming,
export type ResourceTimingDeprecated = [
/*type:*/ Type.ResourceTimingDeprecated,
/*timestamp:*/ number,
/*duration:*/ number,
/*ttfb:*/ number,

View file

@ -548,7 +548,7 @@ export function SetNodeAttributeDict(
]
}
export function ResourceTimingDeprecated(
export function ResourceTimingDeprecatedDeprecated(
timestamp: number,
duration: number,
ttfb: number,
@ -557,9 +557,9 @@ export function ResourceTimingDeprecated(
decodedBodySize: number,
url: string,
initiator: string,
): Messages.ResourceTimingDeprecated {
): Messages.ResourceTimingDeprecatedDeprecated {
return [
Messages.Type.ResourceTimingDeprecated,
Messages.Type.ResourceTimingDeprecatedDeprecated,
timestamp,
duration,
ttfb,
@ -974,7 +974,7 @@ export function UnbindNodes(
]
}
export function ResourceTiming(
export function ResourceTimingDeprecated(
timestamp: number,
duration: number,
ttfb: number,
@ -985,9 +985,9 @@ export function ResourceTiming(
initiator: string,
transferredSize: number,
cached: boolean,
): Messages.ResourceTiming {
): Messages.ResourceTimingDeprecated {
return [
Messages.Type.ResourceTiming,
Messages.Type.ResourceTimingDeprecated,
timestamp,
duration,
ttfb,
@ -1096,4 +1096,3 @@ export function WebVitals(
value,
]
}

View file

@ -60,7 +60,7 @@ export default function (app: App): void {
const sendImgError = app.safe(function (img: HTMLImageElement): void {
const resolvedSrc = resolveURL(img.src || '') // Src type is null sometimes. - is it true?
if (isURL(resolvedSrc)) {
app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false))
app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false, 0, 0, 0, 0, 0, 0))
}
})

View file

@ -123,19 +123,37 @@ export default function (app: App, opts: Partial<Options>): void {
}
const failed = entry.responseEnd === 0
|| (entry.transferSize === 0 && entry.decodedBodySize === 0)
|| (entry.responseStatus && entry.responseStatus >= 400)
const timings = {
queueing: entry.domainLookupStart - entry.startTime,
dnsLookup: entry.domainLookupEnd - entry.domainLookupStart,
initialConnection: entry.connectEnd - entry.connectStart,
ssl: entry.secureConnectionStart > 0
? entry.connectEnd - entry.secureConnectionStart : 0,
ttfb: entry.responseStart - entry.requestStart,
contentDownload: entry.responseEnd - entry.responseStart,
total: entry.responseEnd - entry.startTime
};
if (failed) {
app.send(
ResourceTiming(
entry.startTime + getTimeOrigin(),
0,
0,
timings.ttfb,
0,
0,
0,
entry.name,
entry.initiatorType,
0,
true,
false,
timings.queueing,
timings.dnsLookup,
timings.initialConnection,
timings.ssl,
timings.contentDownload,
timings.total,
),
)
}
@ -143,15 +161,20 @@ export default function (app: App, opts: Partial<Options>): void {
ResourceTiming(
entry.startTime + getTimeOrigin(),
entry.duration,
entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0,
timings.ttfb,
entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0,
entry.encodedBodySize || 0,
entry.decodedBodySize || 0,
app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entry.name,
entry.initiatorType,
entry.transferSize,
// @ts-ignore
(entry.responseStatus && entry.responseStatus === 304) || entry.transferSize === 0,
timings.queueing,
timings.dnsLookup,
timings.initialConnection,
timings.ssl,
timings.contentDownload,
timings.total,
),
)
}

View file

@ -182,7 +182,7 @@ export default class MessageEncoder extends PrimitiveEncoder {
return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3])
break
case Messages.Type.ResourceTimingDeprecated:
case Messages.Type.ResourceTimingDeprecatedDeprecated:
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8])
break
@ -302,7 +302,7 @@ export default class MessageEncoder extends PrimitiveEncoder {
return this.uint(msg[1])
break
case Messages.Type.ResourceTiming:
case Messages.Type.ResourceTimingDeprecated:
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) && this.uint(msg[9]) && this.boolean(msg[10])
break