@rozenite/network-activity-plugin 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20975,6 +20975,9 @@ const symbolicateInitiator = async (initiator, symbolicateStackTrace = symbolica
20975
20975
  }
20976
20976
  };
20977
20977
  const STORE_VERSION = 1;
20978
+ const getElapsedDuration = (endTimestamp, startTimestamp) => {
20979
+ return Math.max(endTimestamp - startTimestamp, 0);
20980
+ };
20978
20981
  const createNetworkActivityStore = () => createStore()(
20979
20982
  persist(
20980
20983
  (set, get) => ({
@@ -21169,6 +21172,10 @@ const createNetworkActivityStore = () => createStore()(
21169
21172
  const updatedEntry = {
21170
21173
  ...httpEntry,
21171
21174
  status: "failed",
21175
+ duration: getElapsedDuration(
21176
+ eventData.timestamp,
21177
+ httpEntry.timestamp
21178
+ ),
21172
21179
  error: eventData.error
21173
21180
  };
21174
21181
  const newEntries = new Map(state.networkEntries);
@@ -21259,7 +21266,10 @@ const createNetworkActivityStore = () => createStore()(
21259
21266
  status: "closed",
21260
21267
  closeCode: eventData.code,
21261
21268
  closeReason: eventData.reason,
21262
- duration: eventData.timestamp - wsEntry.timestamp
21269
+ duration: getElapsedDuration(
21270
+ eventData.timestamp,
21271
+ wsEntry.timestamp
21272
+ )
21263
21273
  };
21264
21274
  const newEntries = new Map(state.networkEntries);
21265
21275
  newEntries.set(entry.id, updatedEntry);
@@ -21324,6 +21334,10 @@ const createNetworkActivityStore = () => createStore()(
21324
21334
  const updatedEntry = {
21325
21335
  ...wsEntry,
21326
21336
  status: "error",
21337
+ duration: getElapsedDuration(
21338
+ eventData.timestamp,
21339
+ wsEntry.timestamp
21340
+ ),
21327
21341
  error: eventData.error
21328
21342
  };
21329
21343
  const newEntries = new Map(state.networkEntries);
@@ -21408,6 +21422,10 @@ const createNetworkActivityStore = () => createStore()(
21408
21422
  const updatedEntry = {
21409
21423
  ...sseEntry,
21410
21424
  status: "error",
21425
+ duration: getElapsedDuration(
21426
+ eventData.timestamp,
21427
+ sseEntry.timestamp
21428
+ ),
21411
21429
  error: eventData.error.message
21412
21430
  };
21413
21431
  const newEntries = new Map(state.networkEntries);
@@ -21425,7 +21443,10 @@ const createNetworkActivityStore = () => createStore()(
21425
21443
  const updatedEntry = {
21426
21444
  ...sseEntry,
21427
21445
  status: "closed",
21428
- duration: eventData.timestamp - sseEntry.timestamp
21446
+ duration: getElapsedDuration(
21447
+ eventData.timestamp,
21448
+ sseEntry.timestamp
21449
+ )
21429
21450
  };
21430
21451
  const newEntries = new Map(state.networkEntries);
21431
21452
  newEntries.set(eventData.requestId, updatedEntry);
@@ -21862,6 +21883,7 @@ const getProcessedRequests = memoize((state) => {
21862
21883
  method: httpEntry.request.method,
21863
21884
  httpStatus: httpEntry.response?.status,
21864
21885
  contentType: httpEntry.response?.contentType,
21886
+ ttfb: httpEntry.ttfb,
21865
21887
  progress: httpEntry.progress
21866
21888
  });
21867
21889
  } else if (entry.type === "websocket") {
@@ -21939,9 +21961,232 @@ const useOverrides = () => {
21939
21961
  const useClientUISettings = () => {
21940
21962
  return useNetworkActivityStore((state) => state.clientUISettings);
21941
21963
  };
21964
+ const base64ToBytes = (base64) => {
21965
+ const binary = atob(base64);
21966
+ const bytes = new Uint8Array(binary.length);
21967
+ for (let i = 0; i < binary.length; i++) {
21968
+ bytes[i] = binary.charCodeAt(i);
21969
+ }
21970
+ return bytes;
21971
+ };
21972
+ const base64ToBlob = (base64, contentType) => {
21973
+ const binary = atob(base64);
21974
+ const buffer = new ArrayBuffer(binary.length);
21975
+ const view = new Uint8Array(buffer);
21976
+ for (let i = 0; i < binary.length; i++) {
21977
+ view[i] = binary.charCodeAt(i);
21978
+ }
21979
+ return new Blob([buffer], {
21980
+ type: contentType || "application/octet-stream"
21981
+ });
21982
+ };
21983
+ const CONTENT_TYPE_EXTENSIONS = {
21984
+ "application/pdf": "pdf",
21985
+ "application/zip": "zip",
21986
+ "application/gzip": "gz",
21987
+ "application/json": "json",
21988
+ "application/xml": "xml",
21989
+ "application/javascript": "js",
21990
+ "application/octet-stream": "bin",
21991
+ "image/png": "png",
21992
+ "image/jpeg": "jpg",
21993
+ "image/gif": "gif",
21994
+ "image/webp": "webp",
21995
+ "image/svg+xml": "svg",
21996
+ "image/bmp": "bmp",
21997
+ "image/x-icon": "ico",
21998
+ "audio/mpeg": "mp3",
21999
+ "audio/ogg": "ogg",
22000
+ "audio/wav": "wav",
22001
+ "video/mp4": "mp4",
22002
+ "video/webm": "webm",
22003
+ "font/woff": "woff",
22004
+ "font/woff2": "woff2",
22005
+ "font/ttf": "ttf",
22006
+ "font/otf": "otf",
22007
+ "text/html": "html",
22008
+ "text/plain": "txt",
22009
+ "text/css": "css",
22010
+ "text/csv": "csv"
22011
+ };
22012
+ const extensionForContentType = (contentType) => {
22013
+ const bare = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
22014
+ return CONTENT_TYPE_EXTENSIONS[bare] ?? "bin";
22015
+ };
22016
+ const readHeader = (headers, name) => {
22017
+ if (!headers) return void 0;
22018
+ const lowerTarget = name.toLowerCase();
22019
+ for (const [key, value] of Object.entries(headers)) {
22020
+ if (key.toLowerCase() === lowerTarget) {
22021
+ return Array.isArray(value) ? value[0] : value;
22022
+ }
22023
+ }
22024
+ return void 0;
22025
+ };
22026
+ const parseContentDispositionFilename = (header) => {
22027
+ if (!header) return void 0;
22028
+ const extended = /filename\*\s*=\s*[^']*''([^;]+)/i.exec(header);
22029
+ if (extended?.[1]) {
22030
+ try {
22031
+ return decodeURIComponent(extended[1].trim()) || void 0;
22032
+ } catch {
22033
+ }
22034
+ }
22035
+ const basic2 = /filename\s*=\s*("([^"]*)"|([^;]+))/i.exec(header);
22036
+ const value = basic2?.[2] ?? basic2?.[3];
22037
+ return value?.trim() || void 0;
22038
+ };
22039
+ const filenameFromUrl = (url) => {
22040
+ try {
22041
+ const parsed = new URL(url);
22042
+ const segments = parsed.pathname.split("/").filter(Boolean);
22043
+ const last = segments[segments.length - 1];
22044
+ return last && last.length > 0 ? last : void 0;
22045
+ } catch {
22046
+ return void 0;
22047
+ }
22048
+ };
22049
+ const deriveFilename = ({
22050
+ headers,
22051
+ url,
22052
+ contentType
22053
+ }) => {
22054
+ const fromDisposition = parseContentDispositionFilename(
22055
+ readHeader(headers, "Content-Disposition")
22056
+ );
22057
+ if (fromDisposition) return fromDisposition;
22058
+ const fromUrl = filenameFromUrl(url);
22059
+ if (fromUrl) return fromUrl;
22060
+ return `response.${extensionForContentType(contentType)}`;
22061
+ };
22062
+ const downloadBlob = (blob, filename) => {
22063
+ const objectUrl = URL.createObjectURL(blob);
22064
+ const anchor = document.createElement("a");
22065
+ anchor.href = objectUrl;
22066
+ anchor.download = filename;
22067
+ document.body.appendChild(anchor);
22068
+ anchor.click();
22069
+ document.body.removeChild(anchor);
22070
+ setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
22071
+ };
22072
+ const downloadJson = (data, filename) => {
22073
+ downloadBlob(
22074
+ new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }),
22075
+ filename
22076
+ );
22077
+ };
22078
+ const EXPORT_SCHEMA_VERSION = 1;
22079
+ const getDuration = (duration) => duration ?? null;
22080
+ const serializeHttpEntry = (entry) => ({
22081
+ id: entry.id,
22082
+ type: "http",
22083
+ source: entry.source,
22084
+ timestamp: entry.timestamp,
22085
+ duration: getDuration(entry.duration),
22086
+ status: entry.status,
22087
+ error: entry.error,
22088
+ canceled: entry.canceled,
22089
+ request: entry.request,
22090
+ response: entry.response ?? null,
22091
+ size: entry.size ?? null,
22092
+ ttfb: entry.ttfb ?? null,
22093
+ initiator: entry.initiator,
22094
+ resourceType: entry.resourceType,
22095
+ progress: entry.progress
22096
+ });
22097
+ const serializeWebSocketEntry = (entry, websocketMessages) => ({
22098
+ id: entry.id,
22099
+ type: "websocket",
22100
+ source: entry.source,
22101
+ timestamp: entry.timestamp,
22102
+ duration: getDuration(entry.duration),
22103
+ status: entry.status,
22104
+ connection: entry.connection,
22105
+ error: entry.error,
22106
+ closeCode: entry.closeCode,
22107
+ closeReason: entry.closeReason,
22108
+ messages: websocketMessages.get(entry.id) ?? []
22109
+ });
22110
+ const serializeSSEEntry = (entry) => ({
22111
+ id: entry.id,
22112
+ type: "sse",
22113
+ source: entry.source,
22114
+ timestamp: entry.timestamp,
22115
+ duration: getDuration(entry.duration),
22116
+ status: entry.status,
22117
+ error: entry.error,
22118
+ request: entry.request,
22119
+ response: entry.response ?? null,
22120
+ initiator: entry.initiator,
22121
+ resourceType: entry.resourceType,
22122
+ messages: entry.messages
22123
+ });
22124
+ const serializeEntry = (entry, websocketMessages) => {
22125
+ switch (entry.type) {
22126
+ case "http":
22127
+ return serializeHttpEntry(entry);
22128
+ case "websocket":
22129
+ return serializeWebSocketEntry(entry, websocketMessages);
22130
+ case "sse":
22131
+ return serializeSSEEntry(entry);
22132
+ }
22133
+ };
22134
+ const createNetworkActivitySessionExport = (networkEntries, websocketMessages, exportedAt = /* @__PURE__ */ new Date()) => {
22135
+ const entries = Array.from(networkEntries.values()).sort((a, b2) => a.timestamp - b2.timestamp).map((entry) => serializeEntry(entry, websocketMessages));
22136
+ return {
22137
+ schemaVersion: EXPORT_SCHEMA_VERSION,
22138
+ tool: "rozenite-network-activity",
22139
+ exportedAt: exportedAt.toISOString(),
22140
+ summary: {
22141
+ totalEntries: entries.length,
22142
+ httpRequests: entries.filter((entry) => entry.type === "http").length,
22143
+ webSocketConnections: entries.filter(
22144
+ (entry) => entry.type === "websocket"
22145
+ ).length,
22146
+ sseConnections: entries.filter((entry) => entry.type === "sse").length,
22147
+ realtimeMessages: entries.reduce((count2, entry) => {
22148
+ if (entry.type === "websocket" || entry.type === "sse") {
22149
+ return count2 + entry.messages.length;
22150
+ }
22151
+ return count2;
22152
+ }, 0)
22153
+ },
22154
+ entries
22155
+ };
22156
+ };
22157
+ const getNetworkActivitySessionExportFileName = (exportedAt = /* @__PURE__ */ new Date()) => {
22158
+ const timestamp = exportedAt.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:]/g, "-");
22159
+ return `rozenite-network-session-${timestamp}.json`;
22160
+ };
22161
+ const useNetworkActivitySessionExport = () => {
22162
+ const canExportSession = useNetworkActivityStore(
22163
+ (state) => state.networkEntries.size > 0
22164
+ );
22165
+ const exportSession = reactExports.useCallback(() => {
22166
+ const { networkEntries, websocketMessages } = store.getState();
22167
+ if (networkEntries.size === 0) {
22168
+ return;
22169
+ }
22170
+ const exportedAt = /* @__PURE__ */ new Date();
22171
+ const exportData = createNetworkActivitySessionExport(
22172
+ networkEntries,
22173
+ websocketMessages,
22174
+ exportedAt
22175
+ );
22176
+ downloadJson(
22177
+ exportData,
22178
+ getNetworkActivitySessionExportFileName(exportedAt)
22179
+ );
22180
+ }, []);
22181
+ return {
22182
+ canExportSession,
22183
+ exportSession
22184
+ };
22185
+ };
21942
22186
  const Toolbar = () => {
21943
22187
  const actions = useNetworkActivityActions();
21944
22188
  const isRecording = useIsRecording();
22189
+ const { canExportSession, exportSession } = useNetworkActivitySessionExport();
21945
22190
  const onToggleRecording = () => {
21946
22191
  actions.setRecording(!isRecording);
21947
22192
  };
@@ -21969,6 +22214,18 @@ const Toolbar = () => {
21969
22214
  className: "h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
21970
22215
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "h-4 w-4" })
21971
22216
  }
22217
+ ),
22218
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
22219
+ Button,
22220
+ {
22221
+ variant: "ghost",
22222
+ size: "sm",
22223
+ onClick: exportSession,
22224
+ disabled: !canExportSession,
22225
+ className: "ml-auto h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
22226
+ title: "Export session",
22227
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "h-4 w-4" })
22228
+ }
21972
22229
  )
21973
22230
  ] });
21974
22231
  };
@@ -24755,7 +25012,7 @@ const formatStartTime = (startTime) => {
24755
25012
  const milliseconds = date.getMilliseconds().toString().padStart(3, "0");
24756
25013
  return `${timeString}.${milliseconds}`;
24757
25014
  };
24758
- const extractDomainAndPath = (url) => {
25015
+ const extractDomainAndPath$1 = (url) => {
24759
25016
  try {
24760
25017
  const { hostname, pathname, search, hash: hash2, port } = new URL(url);
24761
25018
  return {
@@ -24806,122 +25063,9 @@ const sortTime = (rowA, rowB, columnId) => {
24806
25063
  };
24807
25064
  return getNumericValue(a) - getNumericValue(b2);
24808
25065
  };
24809
- const parseThreshold = (value) => {
24810
- const normalizedValue = value.trim();
24811
- if (!normalizedValue) {
24812
- return null;
24813
- }
24814
- const parsedValue = Number(normalizedValue);
24815
- return Number.isFinite(parsedValue) ? parsedValue : null;
24816
- };
24817
- const matchesStatusFilter = (statusCode, statusFilter) => {
24818
- const normalizedFilter = statusFilter.trim().toLowerCase();
24819
- if (!normalizedFilter) {
24820
- return true;
24821
- }
24822
- if (statusCode === void 0) {
24823
- return false;
24824
- }
24825
- const statusRangeMatch = normalizedFilter.match(/^(\d{3})\s*-\s*(\d{3})$/);
24826
- if (statusRangeMatch) {
24827
- const min2 = Number(statusRangeMatch[1]);
24828
- const max2 = Number(statusRangeMatch[2]);
24829
- return statusCode >= min2 && statusCode <= max2;
24830
- }
24831
- const statusClassMatch = normalizedFilter.match(/^([1-5])xx$/);
24832
- if (statusClassMatch) {
24833
- return Math.floor(statusCode / 100) === Number(statusClassMatch[1]);
24834
- }
24835
- const comparisonMatch = normalizedFilter.match(/^(>=|<=|>|<)\s*(\d{3})$/);
24836
- if (comparisonMatch) {
24837
- const value = Number(comparisonMatch[2]);
24838
- switch (comparisonMatch[1]) {
24839
- case ">=":
24840
- return statusCode >= value;
24841
- case "<=":
24842
- return statusCode <= value;
24843
- case ">":
24844
- return statusCode > value;
24845
- case "<":
24846
- return statusCode < value;
24847
- }
24848
- }
24849
- return statusCode === Number(normalizedFilter);
24850
- };
24851
- const isInFlightStatus = (status) => {
24852
- return ["pending", "loading", "connecting", "open"].includes(status);
24853
- };
24854
- const isFailedStatus = (status) => {
24855
- return ["failed", "error"].includes(status);
24856
- };
24857
- const isHttpMethod = (method) => method !== "WS" && method !== "SSE";
24858
- const filterNetworkRequests = (requests, filter) => {
24859
- const searchText = filter.text.trim().toLowerCase();
24860
- const domainFilter = filter.advanced.domain.trim().toLowerCase();
24861
- const contentTypeFilter = filter.advanced.contentType.trim().toLowerCase();
24862
- const minSize = parseThreshold(filter.advanced.minSize);
24863
- const maxSize = parseThreshold(filter.advanced.maxSize);
24864
- const minDuration = parseThreshold(filter.advanced.minDuration);
24865
- const maxDuration = parseThreshold(filter.advanced.maxDuration);
24866
- return requests.filter((request) => {
24867
- if (filter.types.size > 0 && !filter.types.has(request.type)) {
24868
- return false;
24869
- }
24870
- if (filter.advanced.methods.size > 0 && (!isHttpMethod(request.method) || !filter.advanced.methods.has(request.method))) {
24871
- return false;
24872
- }
24873
- if (filter.advanced.sources.size > 0 && (!request.source || !filter.advanced.sources.has(request.source))) {
24874
- return false;
24875
- }
24876
- if (!matchesStatusFilter(request.statusCode, filter.advanced.status)) {
24877
- return false;
24878
- }
24879
- if (domainFilter && !request.domain.toLowerCase().includes(domainFilter)) {
24880
- return false;
24881
- }
24882
- if (contentTypeFilter && !request.contentType?.toLowerCase().includes(contentTypeFilter)) {
24883
- return false;
24884
- }
24885
- if (filter.advanced.failedOnly && !isFailedStatus(request.statusState)) {
24886
- return false;
24887
- }
24888
- if (filter.advanced.inFlightOnly && !isInFlightStatus(request.statusState)) {
24889
- return false;
24890
- }
24891
- if (filter.advanced.overriddenOnly && !request.hasOverride) {
24892
- return false;
24893
- }
24894
- if (minSize !== null && (request.sizeBytes === null || request.sizeBytes < minSize)) {
24895
- return false;
24896
- }
24897
- if (maxSize !== null && (request.sizeBytes === null || request.sizeBytes > maxSize)) {
24898
- return false;
24899
- }
24900
- if (minDuration !== null && request.durationMs < minDuration) {
24901
- return false;
24902
- }
24903
- if (maxDuration !== null && request.durationMs > maxDuration) {
24904
- return false;
24905
- }
24906
- if (searchText) {
24907
- const searchableFields = [
24908
- request.name,
24909
- request.method,
24910
- request.status,
24911
- request.domain,
24912
- request.path,
24913
- request.source,
24914
- request.type,
24915
- request.contentType
24916
- ].join(" ").toLowerCase();
24917
- return searchableFields.includes(searchText);
24918
- }
24919
- return true;
24920
- });
24921
- };
24922
25066
  const processNetworkRequests = (processedRequests, overrides, showEntirePathAsName = false) => {
24923
25067
  return processedRequests.map((request) => {
24924
- const { domain, path } = extractDomainAndPath(request.name);
25068
+ const { domain, path } = extractDomainAndPath$1(request.name);
24925
25069
  const duration = request.duration || 0;
24926
25070
  const hasOverride = overrides.has(request.name);
24927
25071
  let statusDisplay = request.httpStatus || request.status;
@@ -24936,7 +25080,6 @@ const processNetworkRequests = (processedRequests, overrides, showEntirePathAsNa
24936
25080
  name: generateName(request.name, showEntirePathAsName),
24937
25081
  status: statusDisplay,
24938
25082
  statusCode: request.httpStatus || void 0,
24939
- statusState: request.status,
24940
25083
  method: request.method,
24941
25084
  domain,
24942
25085
  path,
@@ -25002,21 +25145,19 @@ const columns$1 = [
25002
25145
  sortingFn: sortTime
25003
25146
  })
25004
25147
  ];
25005
- const RequestList = ({ filter }) => {
25148
+ const RequestList = ({ requests: filteredRequests }) => {
25006
25149
  const actions = useNetworkActivityActions();
25007
- const processedRequests = useProcessedRequests();
25008
25150
  const selectedRequestId = useSelectedRequestId();
25009
25151
  const [sorting, setSorting] = reactExports.useState([]);
25010
25152
  const overrides = useOverrides();
25011
25153
  const clientUISettings = useClientUISettings();
25012
25154
  const requests = reactExports.useMemo(() => {
25013
- const allRequests = processNetworkRequests(
25014
- processedRequests,
25155
+ return processNetworkRequests(
25156
+ filteredRequests,
25015
25157
  overrides,
25016
25158
  clientUISettings?.showUrlAsName
25017
25159
  );
25018
- return filterNetworkRequests(allRequests, filter);
25019
- }, [processedRequests, overrides, clientUISettings?.showUrlAsName, filter]);
25160
+ }, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
25020
25161
  const table = useReactTable({
25021
25162
  data: requests,
25022
25163
  columns: columns$1,
@@ -25879,7 +26020,7 @@ const TabsContent = reactExports.forwardRef(({ className, ...props }, ref) => /*
25879
26020
  }
25880
26021
  ));
25881
26022
  TabsContent.displayName = Content$1.displayName;
25882
- function clamp$1(value, [min2, max2]) {
26023
+ function clamp$2(value, [min2, max2]) {
25883
26024
  return Math.min(max2, Math.max(min2, value));
25884
26025
  }
25885
26026
  function useStateMachine(initialState, machine) {
@@ -26529,7 +26670,7 @@ function getThumbOffsetFromScroll(scrollPos, sizes, dir = "ltr") {
26529
26670
  const maxScrollPos = sizes.content - sizes.viewport;
26530
26671
  const maxThumbPos = scrollbar - thumbSizePx;
26531
26672
  const scrollClampRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0];
26532
- const scrollWithoutMomentum = clamp$1(scrollPos, scrollClampRange);
26673
+ const scrollWithoutMomentum = clamp$2(scrollPos, scrollClampRange);
26533
26674
  const interpolate = linearScale([0, maxScrollPos], [0, maxThumbPos]);
26534
26675
  return interpolate(scrollWithoutMomentum);
26535
26676
  }
@@ -27272,7 +27413,7 @@ const oppositeAlignmentMap = {
27272
27413
  start: "end",
27273
27414
  end: "start"
27274
27415
  };
27275
- function clamp(start, value, end) {
27416
+ function clamp$1(start, value, end) {
27276
27417
  return max(start, min(value, end));
27277
27418
  }
27278
27419
  function evaluate(value, param) {
@@ -27623,7 +27764,7 @@ const arrow$3 = (options) => ({
27623
27764
  const min$12 = minPadding;
27624
27765
  const max2 = clientSize - arrowDimensions[length] - maxPadding;
27625
27766
  const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
27626
- const offset2 = clamp(min$12, center, max2);
27767
+ const offset2 = clamp$1(min$12, center, max2);
27627
27768
  const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset2 && rects.reference[length] / 2 - (center < min$12 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
27628
27769
  const alignmentOffset = shouldAddOffset ? center < min$12 ? center - min$12 : center - max2 : 0;
27629
27770
  return {
@@ -27920,14 +28061,14 @@ const shift$2 = function(options) {
27920
28061
  const maxSide = mainAxis === "y" ? "bottom" : "right";
27921
28062
  const min2 = mainAxisCoord + overflow[minSide];
27922
28063
  const max2 = mainAxisCoord - overflow[maxSide];
27923
- mainAxisCoord = clamp(min2, mainAxisCoord, max2);
28064
+ mainAxisCoord = clamp$1(min2, mainAxisCoord, max2);
27924
28065
  }
27925
28066
  if (checkCrossAxis) {
27926
28067
  const minSide = crossAxis === "y" ? "top" : "left";
27927
28068
  const maxSide = crossAxis === "y" ? "bottom" : "right";
27928
28069
  const min2 = crossAxisCoord + overflow[minSide];
27929
28070
  const max2 = crossAxisCoord - overflow[maxSide];
27930
- crossAxisCoord = clamp(min2, crossAxisCoord, max2);
28071
+ crossAxisCoord = clamp$1(min2, crossAxisCoord, max2);
27931
28072
  }
27932
28073
  const limitedCoords = limiter.fn({
27933
28074
  ...state,
@@ -38796,122 +38937,14 @@ const HexView = ({ bytes }) => {
38796
38937
  }
38797
38938
  ) });
38798
38939
  };
38799
- const base64ToBytes = (base64) => {
38800
- const binary = atob(base64);
38801
- const bytes = new Uint8Array(binary.length);
38802
- for (let i = 0; i < binary.length; i++) {
38803
- bytes[i] = binary.charCodeAt(i);
38940
+ const formatBytes$1 = (bytes) => {
38941
+ if (bytes >= 1024 * 1024) {
38942
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
38804
38943
  }
38805
- return bytes;
38806
- };
38807
- const base64ToBlob = (base64, contentType) => {
38808
- const binary = atob(base64);
38809
- const buffer = new ArrayBuffer(binary.length);
38810
- const view = new Uint8Array(buffer);
38811
- for (let i = 0; i < binary.length; i++) {
38812
- view[i] = binary.charCodeAt(i);
38944
+ if (bytes >= 1024) {
38945
+ return `${(bytes / 1024).toFixed(1)} KB`;
38813
38946
  }
38814
- return new Blob([buffer], {
38815
- type: contentType || "application/octet-stream"
38816
- });
38817
- };
38818
- const CONTENT_TYPE_EXTENSIONS = {
38819
- "application/pdf": "pdf",
38820
- "application/zip": "zip",
38821
- "application/gzip": "gz",
38822
- "application/json": "json",
38823
- "application/xml": "xml",
38824
- "application/javascript": "js",
38825
- "application/octet-stream": "bin",
38826
- "image/png": "png",
38827
- "image/jpeg": "jpg",
38828
- "image/gif": "gif",
38829
- "image/webp": "webp",
38830
- "image/svg+xml": "svg",
38831
- "image/bmp": "bmp",
38832
- "image/x-icon": "ico",
38833
- "audio/mpeg": "mp3",
38834
- "audio/ogg": "ogg",
38835
- "audio/wav": "wav",
38836
- "video/mp4": "mp4",
38837
- "video/webm": "webm",
38838
- "font/woff": "woff",
38839
- "font/woff2": "woff2",
38840
- "font/ttf": "ttf",
38841
- "font/otf": "otf",
38842
- "text/html": "html",
38843
- "text/plain": "txt",
38844
- "text/css": "css",
38845
- "text/csv": "csv"
38846
- };
38847
- const extensionForContentType = (contentType) => {
38848
- const bare = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
38849
- return CONTENT_TYPE_EXTENSIONS[bare] ?? "bin";
38850
- };
38851
- const readHeader = (headers, name) => {
38852
- if (!headers) return void 0;
38853
- const lowerTarget = name.toLowerCase();
38854
- for (const [key, value] of Object.entries(headers)) {
38855
- if (key.toLowerCase() === lowerTarget) {
38856
- return Array.isArray(value) ? value[0] : value;
38857
- }
38858
- }
38859
- return void 0;
38860
- };
38861
- const parseContentDispositionFilename = (header) => {
38862
- if (!header) return void 0;
38863
- const extended = /filename\*\s*=\s*[^']*''([^;]+)/i.exec(header);
38864
- if (extended?.[1]) {
38865
- try {
38866
- return decodeURIComponent(extended[1].trim()) || void 0;
38867
- } catch {
38868
- }
38869
- }
38870
- const basic2 = /filename\s*=\s*("([^"]*)"|([^;]+))/i.exec(header);
38871
- const value = basic2?.[2] ?? basic2?.[3];
38872
- return value?.trim() || void 0;
38873
- };
38874
- const filenameFromUrl = (url) => {
38875
- try {
38876
- const parsed = new URL(url);
38877
- const segments = parsed.pathname.split("/").filter(Boolean);
38878
- const last = segments[segments.length - 1];
38879
- return last && last.length > 0 ? last : void 0;
38880
- } catch {
38881
- return void 0;
38882
- }
38883
- };
38884
- const deriveFilename = ({
38885
- headers,
38886
- url,
38887
- contentType
38888
- }) => {
38889
- const fromDisposition = parseContentDispositionFilename(
38890
- readHeader(headers, "Content-Disposition")
38891
- );
38892
- if (fromDisposition) return fromDisposition;
38893
- const fromUrl = filenameFromUrl(url);
38894
- if (fromUrl) return fromUrl;
38895
- return `response.${extensionForContentType(contentType)}`;
38896
- };
38897
- const downloadBlob = (blob, filename) => {
38898
- const objectUrl = URL.createObjectURL(blob);
38899
- const anchor = document.createElement("a");
38900
- anchor.href = objectUrl;
38901
- anchor.download = filename;
38902
- document.body.appendChild(anchor);
38903
- anchor.click();
38904
- document.body.removeChild(anchor);
38905
- setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
38906
- };
38907
- const formatBytes$1 = (bytes) => {
38908
- if (bytes >= 1024 * 1024) {
38909
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
38910
- }
38911
- if (bytes >= 1024) {
38912
- return `${(bytes / 1024).toFixed(1)} KB`;
38913
- }
38914
- return `${bytes} bytes`;
38947
+ return `${bytes} bytes`;
38915
38948
  };
38916
38949
  const decodedByteCount = (base64) => {
38917
38950
  const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
@@ -41730,19 +41763,14 @@ const Input = reactExports.forwardRef(
41730
41763
  }
41731
41764
  );
41732
41765
  Input.displayName = "Input";
41733
- const REQUEST_TYPES = ["http", "sse", "websocket"];
41734
- const HTTP_METHODS = [
41735
- "GET",
41736
- "POST",
41737
- "PUT",
41738
- "PATCH",
41739
- "DELETE",
41740
- "HEAD"
41766
+ const DEFAULT_REQUEST_TYPES = [
41767
+ "http",
41768
+ "websocket",
41769
+ "sse"
41741
41770
  ];
41742
- const SOURCES = ["builtin", "nitro"];
41743
41771
  const createDefaultFilter = () => ({
41744
41772
  text: "",
41745
- types: /* @__PURE__ */ new Set(),
41773
+ types: new Set(DEFAULT_REQUEST_TYPES),
41746
41774
  advanced: {
41747
41775
  methods: /* @__PURE__ */ new Set(),
41748
41776
  sources: /* @__PURE__ */ new Set(),
@@ -41758,6 +41786,15 @@ const createDefaultFilter = () => ({
41758
41786
  maxDuration: ""
41759
41787
  }
41760
41788
  });
41789
+ const HTTP_METHODS = [
41790
+ "GET",
41791
+ "POST",
41792
+ "PUT",
41793
+ "PATCH",
41794
+ "DELETE",
41795
+ "HEAD"
41796
+ ];
41797
+ const SOURCES = ["builtin", "nitro"];
41761
41798
  const getTypeLabel = (type) => {
41762
41799
  switch (type) {
41763
41800
  case "http":
@@ -41793,7 +41830,7 @@ const getAdvancedFilterCount = (advanced) => {
41793
41830
  ].filter(Boolean).length;
41794
41831
  };
41795
41832
  const getActiveFilterCount = (filter) => {
41796
- const typeFilterCount = filter.types.size > 0 ? 1 : 0;
41833
+ const typeFilterCount = filter.types.size < DEFAULT_REQUEST_TYPES.length ? 1 : 0;
41797
41834
  return typeFilterCount + getAdvancedFilterCount(filter.advanced);
41798
41835
  };
41799
41836
  const FilterField = ({
@@ -41904,7 +41941,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
41904
41941
  onFilterChange(createDefaultFilter());
41905
41942
  };
41906
41943
  const activeFilterCount = getActiveFilterCount(filter);
41907
- const hasActiveFilters = filter.text !== "" || activeFilterCount > 0;
41944
+ const hasActiveFilters = filter.text.trim() !== "" || activeFilterCount > 0;
41908
41945
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 p-2 border-b border-gray-700 bg-gray-800", children: [
41909
41946
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
41910
41947
  Input,
@@ -41940,7 +41977,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
41940
41977
  ...getFloatingProps(),
41941
41978
  children: [
41942
41979
  /* @__PURE__ */ jsxRuntimeExports.jsx(FilterPanelLabel, { children: "Request Type" }),
41943
- REQUEST_TYPES.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
41980
+ DEFAULT_REQUEST_TYPES.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
41944
41981
  FilterCheckbox,
41945
41982
  {
41946
41983
  checked: filter.types.has(type),
@@ -42077,14 +42114,692 @@ const FilterBar = ({ filter, onFilterChange }) => {
42077
42114
  )
42078
42115
  ] });
42079
42116
  };
42117
+ const TIMELINE_LAYOUT = {
42118
+ minVisibleBarPercent: 0.65,
42119
+ minRangeMs: 1e3,
42120
+ liveRefreshMs: 1e3,
42121
+ maxRenderedRequests: 1e3,
42122
+ laneCount: 8,
42123
+ laneHeightPx: 2,
42124
+ laneGapPx: 6,
42125
+ laneHitTargetHeightPx: 8,
42126
+ rulerHeightPx: 22,
42127
+ laneTopPx: 32,
42128
+ laneBottomPaddingPx: 18,
42129
+ tickTargetCount: 7,
42130
+ minTickLabelGapPercent: 6,
42131
+ rangePaddingRatio: 0.025,
42132
+ minRangePaddingMs: 25,
42133
+ streamingRequestMaxDurationMs: 5e3
42134
+ };
42135
+ const NICE_TICK_FACTORS = [1, 2, 2.5, 5, 10];
42136
+ const ACTIVE_HTTP_STATUSES = /* @__PURE__ */ new Set([
42137
+ "pending",
42138
+ "loading"
42139
+ ]);
42140
+ const ACTIVE_WEBSOCKET_STATUSES = /* @__PURE__ */ new Set([
42141
+ "connecting",
42142
+ "open",
42143
+ "closing"
42144
+ ]);
42145
+ const ACTIVE_SSE_STATUSES = /* @__PURE__ */ new Set([
42146
+ "connecting",
42147
+ "open"
42148
+ ]);
42149
+ const clamp = (value, minimum, maximum) => {
42150
+ return Math.min(Math.max(value, minimum), maximum);
42151
+ };
42152
+ const getTimelineChartHeight = (layout = TIMELINE_LAYOUT) => {
42153
+ return layout.laneTopPx + layout.laneCount * layout.laneHeightPx + (layout.laneCount - 1) * layout.laneGapPx + layout.laneBottomPaddingPx;
42154
+ };
42155
+ const getTimelineLaneTop = (lane, layout = TIMELINE_LAYOUT) => {
42156
+ return lane * (layout.laneHeightPx + layout.laneGapPx) + layout.laneTopPx;
42157
+ };
42158
+ const getTimelineTrackTop = (lane, layout = TIMELINE_LAYOUT) => {
42159
+ const visualBarTop = getTimelineLaneTop(lane, layout);
42160
+ return visualBarTop - (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
42161
+ };
42162
+ const getTimelineBarTopOffset = (layout = TIMELINE_LAYOUT) => {
42163
+ return (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
42164
+ };
42165
+ const isRequestActive = (request) => {
42166
+ switch (request.type) {
42167
+ case "http":
42168
+ return ACTIVE_HTTP_STATUSES.has(request.status);
42169
+ case "websocket":
42170
+ return ACTIVE_WEBSOCKET_STATUSES.has(request.status);
42171
+ case "sse":
42172
+ return ACTIVE_SSE_STATUSES.has(request.status);
42173
+ }
42174
+ };
42175
+ const formatTimelineOffset = (milliseconds) => {
42176
+ if (milliseconds < 1e3) {
42177
+ return `${Math.round(milliseconds)} ms`;
42178
+ }
42179
+ if (milliseconds < 6e4) {
42180
+ return `${(milliseconds / 1e3).toFixed(milliseconds < 1e4 ? 1 : 0)} s`;
42181
+ }
42182
+ const totalSeconds = Math.round(milliseconds / 1e3);
42183
+ const minutes = Math.floor(totalSeconds / 60);
42184
+ const seconds = totalSeconds % 60;
42185
+ return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
42186
+ };
42187
+ const getRequestEndTime = (request, now) => {
42188
+ if (typeof request.duration === "number") {
42189
+ return request.timestamp + Math.max(request.duration, 0);
42190
+ }
42191
+ if (isRequestActive(request)) {
42192
+ return Math.max(now, request.timestamp);
42193
+ }
42194
+ return request.timestamp;
42195
+ };
42196
+ const getTimelineRequestEndTime = (request, now, layout = TIMELINE_LAYOUT) => {
42197
+ const endTime = getRequestEndTime(request, now);
42198
+ if (request.type !== "websocket" && request.type !== "sse") {
42199
+ return endTime;
42200
+ }
42201
+ return Math.min(
42202
+ endTime,
42203
+ request.timestamp + layout.streamingRequestMaxDurationMs
42204
+ );
42205
+ };
42206
+ const requestOverlapsTimelineRange = (request, range, now, layout = TIMELINE_LAYOUT) => {
42207
+ const rangeStart = Math.min(range.startTime, range.endTime);
42208
+ const rangeEnd = Math.max(range.startTime, range.endTime);
42209
+ const requestStart = request.timestamp;
42210
+ const requestEnd = getTimelineRequestEndTime(request, now, layout);
42211
+ return requestStart <= rangeEnd && requestEnd >= rangeStart;
42212
+ };
42213
+ const getNiceTickStep = (rangeDuration, targetTickCount) => {
42214
+ const targetStep = rangeDuration / targetTickCount;
42215
+ const exponent = Math.floor(Math.log10(targetStep));
42216
+ const magnitude = 10 ** exponent;
42217
+ const normalizedStep = targetStep / magnitude;
42218
+ const factor = NICE_TICK_FACTORS.find((candidate) => candidate >= normalizedStep) ?? NICE_TICK_FACTORS[NICE_TICK_FACTORS.length - 1];
42219
+ return factor * magnitude;
42220
+ };
42221
+ const getTimelineTicks = (rangeDuration, layout = TIMELINE_LAYOUT) => {
42222
+ const step = getNiceTickStep(rangeDuration, layout.tickTargetCount);
42223
+ const ticks = [];
42224
+ for (let value = 0; value <= rangeDuration; value += step) {
42225
+ ticks.push({
42226
+ label: formatTimelineOffset(value),
42227
+ offsetPercent: value / rangeDuration * 100
42228
+ });
42229
+ }
42230
+ if (ticks.length === 0 || ticks[ticks.length - 1].offsetPercent < 100 - Number.EPSILON) {
42231
+ const finalTick = {
42232
+ label: formatTimelineOffset(rangeDuration),
42233
+ offsetPercent: 100
42234
+ };
42235
+ const previousTick = ticks[ticks.length - 1];
42236
+ if (!previousTick || finalTick.label !== previousTick.label && finalTick.offsetPercent - previousTick.offsetPercent >= layout.minTickLabelGapPercent) {
42237
+ ticks.push(finalTick);
42238
+ }
42239
+ }
42240
+ return ticks;
42241
+ };
42242
+ const getTimelineBounds = (requests, now, layout) => {
42243
+ return requests.reduce(
42244
+ (result, request) => {
42245
+ const endTime = getTimelineRequestEndTime(request, now, layout);
42246
+ return {
42247
+ start: Math.min(result.start, request.timestamp),
42248
+ end: Math.max(result.end, endTime)
42249
+ };
42250
+ },
42251
+ {
42252
+ start: Number.POSITIVE_INFINITY,
42253
+ end: Number.NEGATIVE_INFINITY
42254
+ }
42255
+ );
42256
+ };
42257
+ const getEarliestLaneIndex = (laneEndTimes) => {
42258
+ return laneEndTimes.reduce((earliestIndex, laneEndTime, index2) => {
42259
+ return laneEndTime < laneEndTimes[earliestIndex] ? index2 : earliestIndex;
42260
+ }, 0);
42261
+ };
42262
+ const getRenderableRequests = (requests, layout) => {
42263
+ if (requests.length <= layout.maxRenderedRequests) {
42264
+ return requests;
42265
+ }
42266
+ return [...requests].sort((a, b2) => b2.timestamp - a.timestamp).slice(0, layout.maxRenderedRequests);
42267
+ };
42268
+ const getTimelineModel = (requests, now, layout = TIMELINE_LAYOUT) => {
42269
+ const renderableRequests = getRenderableRequests(requests, layout);
42270
+ const hiddenRequestCount = requests.length - renderableRequests.length;
42271
+ if (renderableRequests.length === 0) {
42272
+ return {
42273
+ rows: [],
42274
+ ticks: getTimelineTicks(layout.minRangeMs, layout),
42275
+ rangeStart: 0,
42276
+ rangeDuration: layout.minRangeMs,
42277
+ chartHeight: getTimelineChartHeight(layout),
42278
+ totalRequestCount: requests.length,
42279
+ hiddenRequestCount
42280
+ };
42281
+ }
42282
+ const bounds = getTimelineBounds(renderableRequests, now, layout);
42283
+ const rawRangeDuration = Math.max(
42284
+ bounds.end - bounds.start,
42285
+ layout.minRangeMs
42286
+ );
42287
+ const rangePadding = Math.max(
42288
+ rawRangeDuration * layout.rangePaddingRatio,
42289
+ layout.minRangePaddingMs
42290
+ );
42291
+ const rangeStart = bounds.start - rangePadding;
42292
+ const rangeDuration = rawRangeDuration + rangePadding * 2;
42293
+ const laneEndTimes = Array.from({ length: layout.laneCount }, () => 0);
42294
+ const rows = [...renderableRequests].sort((a, b2) => a.timestamp - b2.timestamp).map((request) => {
42295
+ const startTime = request.timestamp;
42296
+ const endTime = getTimelineRequestEndTime(request, now, layout);
42297
+ const duration = Math.max(endTime - startTime, 0);
42298
+ const offsetPercent = clamp(
42299
+ (startTime - rangeStart) / rangeDuration * 100,
42300
+ 0,
42301
+ 100 - layout.minVisibleBarPercent
42302
+ );
42303
+ const widthPercent = Math.min(
42304
+ Math.max(duration / rangeDuration * 100, layout.minVisibleBarPercent),
42305
+ 100 - offsetPercent
42306
+ );
42307
+ const ttfb = clamp(request.ttfb ?? 0, 0, duration);
42308
+ const ttfbPercent = duration === 0 ? 0 : ttfb / duration * 100;
42309
+ const receivePercent = Math.max(100 - ttfbPercent, 0);
42310
+ const availableLane = laneEndTimes.findIndex(
42311
+ (laneEndTime) => laneEndTime <= startTime
42312
+ );
42313
+ const isOverflowingLane = availableLane === -1;
42314
+ const lane = isOverflowingLane ? getEarliestLaneIndex(laneEndTimes) : availableLane;
42315
+ laneEndTimes[lane] = Math.max(laneEndTimes[lane], endTime);
42316
+ return {
42317
+ request,
42318
+ offsetPercent,
42319
+ widthPercent,
42320
+ duration,
42321
+ ttfbPercent,
42322
+ receivePercent,
42323
+ isActive: isRequestActive(request),
42324
+ lane,
42325
+ isOverflowingLane
42326
+ };
42327
+ });
42328
+ return {
42329
+ rows,
42330
+ ticks: getTimelineTicks(rangeDuration, layout),
42331
+ rangeStart,
42332
+ rangeDuration,
42333
+ chartHeight: getTimelineChartHeight(layout),
42334
+ totalRequestCount: requests.length,
42335
+ hiddenRequestCount
42336
+ };
42337
+ };
42338
+ const REQUEST_TIMELINE_COLORS = {
42339
+ error: "bg-red-400",
42340
+ primary: "bg-gray-400",
42341
+ active: "bg-gray-500",
42342
+ httpTtfb: "bg-gray-200"
42343
+ };
42344
+ const getPrimaryBarClassName = (request) => {
42345
+ if (request.status === "failed" || request.status === "error") {
42346
+ return REQUEST_TIMELINE_COLORS.error;
42347
+ }
42348
+ return REQUEST_TIMELINE_COLORS.primary;
42349
+ };
42350
+ const getStyle = (offsetPercent, widthPercent) => ({
42351
+ left: `${offsetPercent}%`,
42352
+ width: `${widthPercent}%`
42353
+ });
42354
+ const GridLines = ({ ticks }) => {
42355
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute inset-0", children: ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42356
+ "div",
42357
+ {
42358
+ className: "absolute inset-y-0 border-l border-gray-800",
42359
+ style: { left: `${tick.offsetPercent}%` }
42360
+ },
42361
+ `${tick.label}-${tick.offsetPercent}`
42362
+ )) });
42363
+ };
42364
+ const getTickLabelStyle = (tick) => {
42365
+ if (tick.offsetPercent === 0) {
42366
+ return {
42367
+ left: 4
42368
+ };
42369
+ }
42370
+ if (tick.offsetPercent === 100) {
42371
+ return {
42372
+ right: 4
42373
+ };
42374
+ }
42375
+ return {
42376
+ left: `${tick.offsetPercent}%`
42377
+ };
42378
+ };
42379
+ const TimelineTrack = ({
42380
+ row,
42381
+ isSelected,
42382
+ onSelect,
42383
+ shouldSuppressSelect
42384
+ }) => {
42385
+ const primaryBarClassName = row.isActive ? REQUEST_TIMELINE_COLORS.active : getPrimaryBarClassName(row.request);
42386
+ const isSplitHttpBar = row.request.type === "http" && row.ttfbPercent > 0 && row.receivePercent > 0;
42387
+ const trackTop = getTimelineTrackTop(row.lane);
42388
+ const barTop = getTimelineBarTopOffset();
42389
+ const positionStyle = {
42390
+ ...getStyle(row.offsetPercent, row.widthPercent),
42391
+ top: trackTop
42392
+ };
42393
+ const durationLabel = row.isActive ? `${formatTimelineOffset(row.duration)}+` : formatTimelineOffset(row.duration);
42394
+ const label = `${row.request.method} ${row.request.name} - ${durationLabel}`;
42395
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
42396
+ "button",
42397
+ {
42398
+ type: "button",
42399
+ "aria-label": label,
42400
+ title: label,
42401
+ "data-timeline-track": "true",
42402
+ className: "absolute rounded-sm text-left transition-opacity hover:opacity-80",
42403
+ style: {
42404
+ ...positionStyle,
42405
+ height: TIMELINE_LAYOUT.laneHitTargetHeightPx
42406
+ },
42407
+ onClick: (event) => {
42408
+ if (shouldSuppressSelect()) {
42409
+ event.preventDefault();
42410
+ event.stopPropagation();
42411
+ return;
42412
+ }
42413
+ onSelect(row.request.id);
42414
+ },
42415
+ children: isSplitHttpBar ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
42416
+ "div",
42417
+ {
42418
+ className: `absolute flex w-full overflow-hidden rounded-sm ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
42419
+ style: {
42420
+ top: barTop,
42421
+ height: TIMELINE_LAYOUT.laneHeightPx
42422
+ },
42423
+ children: [
42424
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42425
+ "div",
42426
+ {
42427
+ className: `h-full ${REQUEST_TIMELINE_COLORS.httpTtfb}`,
42428
+ style: { width: `${row.ttfbPercent}%` }
42429
+ }
42430
+ ),
42431
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42432
+ "div",
42433
+ {
42434
+ className: `h-full ${REQUEST_TIMELINE_COLORS.primary}`,
42435
+ style: { width: `${row.receivePercent}%` }
42436
+ }
42437
+ )
42438
+ ]
42439
+ }
42440
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
42441
+ "div",
42442
+ {
42443
+ className: `absolute w-full rounded-sm ${primaryBarClassName} ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
42444
+ style: {
42445
+ top: barTop,
42446
+ height: TIMELINE_LAYOUT.laneHeightPx
42447
+ }
42448
+ }
42449
+ )
42450
+ }
42451
+ );
42452
+ };
42453
+ const clampPercent = (value) => Math.min(Math.max(value, 0), 100);
42454
+ const getPointerPercent = (event, element) => {
42455
+ const rect = element.getBoundingClientRect();
42456
+ if (rect.width === 0) {
42457
+ return 0;
42458
+ }
42459
+ return clampPercent((event.clientX - rect.left) / rect.width * 100);
42460
+ };
42461
+ const getSelectionStyle = (range, timeline) => {
42462
+ const startPercent = clampPercent(
42463
+ (range.startTime - timeline.rangeStart) / timeline.rangeDuration * 100
42464
+ );
42465
+ const endPercent = clampPercent(
42466
+ (range.endTime - timeline.rangeStart) / timeline.rangeDuration * 100
42467
+ );
42468
+ const left2 = Math.min(startPercent, endPercent);
42469
+ const width = Math.abs(endPercent - startPercent);
42470
+ return {
42471
+ left: `${left2}%`,
42472
+ width: `${width}%`,
42473
+ top: TIMELINE_LAYOUT.rulerHeightPx
42474
+ };
42475
+ };
42476
+ const getDraftSelectionStyle = (draft) => {
42477
+ const left2 = Math.min(draft.anchorPercent, draft.currentPercent);
42478
+ const width = Math.abs(draft.currentPercent - draft.anchorPercent);
42479
+ return {
42480
+ left: `${left2}%`,
42481
+ width: `${width}%`,
42482
+ top: TIMELINE_LAYOUT.rulerHeightPx
42483
+ };
42484
+ };
42485
+ const NetworkTimeline = ({
42486
+ requests,
42487
+ selection,
42488
+ filteredRequestCount,
42489
+ onSelectionChange
42490
+ }) => {
42491
+ const actions = useNetworkActivityActions();
42492
+ const selectedRequestId = useSelectedRequestId();
42493
+ const [now, setNow] = reactExports.useState(() => Date.now());
42494
+ const [draftSelection, setDraftSelection] = reactExports.useState(
42495
+ null
42496
+ );
42497
+ const chartRef = reactExports.useRef(null);
42498
+ const suppressTrackClickRef = reactExports.useRef(false);
42499
+ const hasActiveRequests = requests.some(isRequestActive);
42500
+ reactExports.useEffect(() => {
42501
+ if (!hasActiveRequests) {
42502
+ return;
42503
+ }
42504
+ const interval = window.setInterval(() => {
42505
+ setNow(Date.now());
42506
+ }, TIMELINE_LAYOUT.liveRefreshMs);
42507
+ return () => window.clearInterval(interval);
42508
+ }, [hasActiveRequests]);
42509
+ const timeline = reactExports.useMemo(() => {
42510
+ return getTimelineModel(requests, now);
42511
+ }, [requests, now]);
42512
+ const onRequestSelect = (requestId) => {
42513
+ actions.setSelectedRequest(requestId);
42514
+ };
42515
+ const onPointerDown = (event) => {
42516
+ if (event.button !== 0 || requests.length === 0) {
42517
+ return;
42518
+ }
42519
+ const chartElement = chartRef.current;
42520
+ if (!chartElement) {
42521
+ return;
42522
+ }
42523
+ const percent = getPointerPercent(event, chartElement);
42524
+ const target = event.target;
42525
+ const startedOnTrack = target instanceof Element && target.closest('[data-timeline-track="true"]') !== null;
42526
+ setDraftSelection({
42527
+ anchorPercent: percent,
42528
+ currentPercent: percent,
42529
+ startedOnTrack
42530
+ });
42531
+ chartElement.setPointerCapture(event.pointerId);
42532
+ };
42533
+ const onPointerMove = (event) => {
42534
+ if (!draftSelection) {
42535
+ return;
42536
+ }
42537
+ const chartElement = chartRef.current;
42538
+ if (!chartElement) {
42539
+ return;
42540
+ }
42541
+ event.preventDefault();
42542
+ const percent = getPointerPercent(event, chartElement);
42543
+ setDraftSelection(
42544
+ (current) => current ? { ...current, currentPercent: percent } : current
42545
+ );
42546
+ };
42547
+ const onPointerUp = (event) => {
42548
+ if (!draftSelection) {
42549
+ return;
42550
+ }
42551
+ const chartElement = chartRef.current;
42552
+ const currentPercent = chartElement ? getPointerPercent(event, chartElement) : draftSelection.currentPercent;
42553
+ const distance = Math.abs(currentPercent - draftSelection.anchorPercent);
42554
+ if (distance > 1) {
42555
+ const startOffset = Math.min(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
42556
+ const endOffset = Math.max(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
42557
+ onSelectionChange({
42558
+ startTime: timeline.rangeStart + startOffset,
42559
+ endTime: timeline.rangeStart + endOffset
42560
+ });
42561
+ suppressTrackClickRef.current = true;
42562
+ window.setTimeout(() => {
42563
+ suppressTrackClickRef.current = false;
42564
+ }, 0);
42565
+ } else if (!draftSelection.startedOnTrack) {
42566
+ onSelectionChange(null);
42567
+ }
42568
+ setDraftSelection(null);
42569
+ if (chartElement?.hasPointerCapture(event.pointerId)) {
42570
+ chartElement.releasePointerCapture(event.pointerId);
42571
+ }
42572
+ };
42573
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-b border-gray-700 bg-gray-900 p-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
42574
+ "div",
42575
+ {
42576
+ ref: chartRef,
42577
+ className: "relative overflow-hidden border border-gray-800 bg-gray-950",
42578
+ style: { height: timeline.chartHeight },
42579
+ onPointerDown,
42580
+ onPointerMove,
42581
+ onPointerUp,
42582
+ children: [
42583
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GridLines, { ticks: timeline.ticks }),
42584
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42585
+ "div",
42586
+ {
42587
+ className: "pointer-events-none absolute inset-x-0 border-b border-gray-800",
42588
+ style: { top: TIMELINE_LAYOUT.rulerHeightPx }
42589
+ }
42590
+ ),
42591
+ timeline.ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42592
+ "div",
42593
+ {
42594
+ className: "absolute top-1 whitespace-nowrap tabular-nums text-xs text-gray-200",
42595
+ style: getTickLabelStyle(tick),
42596
+ children: tick.label
42597
+ },
42598
+ `${tick.label}-${tick.offsetPercent}`
42599
+ )),
42600
+ selection && /* @__PURE__ */ jsxRuntimeExports.jsx(
42601
+ "div",
42602
+ {
42603
+ className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/10",
42604
+ style: getSelectionStyle(selection, timeline)
42605
+ }
42606
+ ),
42607
+ draftSelection && /* @__PURE__ */ jsxRuntimeExports.jsx(
42608
+ "div",
42609
+ {
42610
+ className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/15",
42611
+ style: getDraftSelectionStyle(draftSelection)
42612
+ }
42613
+ ),
42614
+ timeline.rows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42615
+ TimelineTrack,
42616
+ {
42617
+ row,
42618
+ isSelected: selectedRequestId === row.request.id,
42619
+ onSelect: onRequestSelect,
42620
+ shouldSuppressSelect: () => suppressTrackClickRef.current
42621
+ },
42622
+ row.request.id
42623
+ )),
42624
+ selection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-1 right-1 flex items-center gap-1 rounded border border-gray-700 bg-gray-900/95 px-1.5 py-0.5 text-xs text-gray-400", children: [
42625
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42626
+ filteredRequestCount,
42627
+ " in range"
42628
+ ] }),
42629
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42630
+ "button",
42631
+ {
42632
+ type: "button",
42633
+ title: "Clear timeline selection",
42634
+ "aria-label": "Clear timeline selection",
42635
+ className: "rounded p-0.5 text-gray-400 hover:bg-gray-800 hover:text-gray-100",
42636
+ onClick: () => onSelectionChange(null),
42637
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { className: "h-3 w-3" })
42638
+ }
42639
+ )
42640
+ ] }),
42641
+ timeline.hiddenRequestCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-1 left-1 rounded border border-gray-700 bg-gray-900/95 px-1.5 py-0.5 text-xs text-gray-400", children: [
42642
+ "Showing latest ",
42643
+ timeline.rows.length,
42644
+ " of",
42645
+ " ",
42646
+ timeline.totalRequestCount
42647
+ ] })
42648
+ ]
42649
+ }
42650
+ ) });
42651
+ };
42652
+ const parseThreshold = (value) => {
42653
+ const normalizedValue = value.trim();
42654
+ if (!normalizedValue) {
42655
+ return null;
42656
+ }
42657
+ const parsedValue = Number(normalizedValue);
42658
+ return Number.isFinite(parsedValue) ? parsedValue : null;
42659
+ };
42660
+ const matchesStatusFilter = (statusCode, statusFilter) => {
42661
+ const normalizedFilter = statusFilter.trim().toLowerCase();
42662
+ if (!normalizedFilter) {
42663
+ return true;
42664
+ }
42665
+ if (statusCode === void 0) {
42666
+ return false;
42667
+ }
42668
+ const statusRangeMatch = normalizedFilter.match(/^(\d{3})\s*-\s*(\d{3})$/);
42669
+ if (statusRangeMatch) {
42670
+ const min2 = Number(statusRangeMatch[1]);
42671
+ const max2 = Number(statusRangeMatch[2]);
42672
+ return statusCode >= min2 && statusCode <= max2;
42673
+ }
42674
+ const statusClassMatch = normalizedFilter.match(/^([1-5])xx$/);
42675
+ if (statusClassMatch) {
42676
+ return Math.floor(statusCode / 100) === Number(statusClassMatch[1]);
42677
+ }
42678
+ const comparisonMatch = normalizedFilter.match(/^(>=|<=|>|<)\s*(\d{3})$/);
42679
+ if (comparisonMatch) {
42680
+ const value = Number(comparisonMatch[2]);
42681
+ switch (comparisonMatch[1]) {
42682
+ case ">=":
42683
+ return statusCode >= value;
42684
+ case "<=":
42685
+ return statusCode <= value;
42686
+ case ">":
42687
+ return statusCode > value;
42688
+ case "<":
42689
+ return statusCode < value;
42690
+ }
42691
+ }
42692
+ return statusCode === Number(normalizedFilter);
42693
+ };
42694
+ const isInFlightStatus = (status) => {
42695
+ return ["pending", "loading", "connecting", "open"].includes(status);
42696
+ };
42697
+ const isFailedStatus = (status) => {
42698
+ return ["failed", "error"].includes(status);
42699
+ };
42700
+ const isHttpMethod = (method) => method !== "WS" && method !== "SSE";
42701
+ const extractDomainAndPath = (url) => {
42702
+ try {
42703
+ const { hostname, pathname, search, hash: hash2, port } = new URL(url);
42704
+ return {
42705
+ domain: `${hostname}${port ? `:${port}` : ""}`,
42706
+ path: `${pathname}${search}${hash2}`
42707
+ };
42708
+ } catch {
42709
+ return { domain: "unknown", path: url };
42710
+ }
42711
+ };
42712
+ const matchesRequestFilter = (request, filter, options = {}) => {
42713
+ if (filter.types.size > 0 && !filter.types.has(request.type)) {
42714
+ return false;
42715
+ }
42716
+ if (filter.advanced.methods.size > 0 && (!isHttpMethod(request.method) || !filter.advanced.methods.has(request.method))) {
42717
+ return false;
42718
+ }
42719
+ if (filter.advanced.sources.size > 0 && (!request.source || !filter.advanced.sources.has(request.source))) {
42720
+ return false;
42721
+ }
42722
+ if (!matchesStatusFilter(request.httpStatus, filter.advanced.status)) {
42723
+ return false;
42724
+ }
42725
+ const { domain, path } = extractDomainAndPath(request.name);
42726
+ const domainFilter = filter.advanced.domain.trim().toLowerCase();
42727
+ if (domainFilter && !domain.toLowerCase().includes(domainFilter)) {
42728
+ return false;
42729
+ }
42730
+ const contentTypeFilter = filter.advanced.contentType.trim().toLowerCase();
42731
+ if (contentTypeFilter && !request.contentType?.toLowerCase().includes(contentTypeFilter)) {
42732
+ return false;
42733
+ }
42734
+ if (filter.advanced.failedOnly && !isFailedStatus(request.status)) {
42735
+ return false;
42736
+ }
42737
+ if (filter.advanced.inFlightOnly && !isInFlightStatus(request.status)) {
42738
+ return false;
42739
+ }
42740
+ if (filter.advanced.overriddenOnly && !options.hasOverride) {
42741
+ return false;
42742
+ }
42743
+ const minSize = parseThreshold(filter.advanced.minSize);
42744
+ if (minSize !== null && (request.size === null || request.size < minSize)) {
42745
+ return false;
42746
+ }
42747
+ const maxSize = parseThreshold(filter.advanced.maxSize);
42748
+ if (maxSize !== null && (request.size === null || request.size > maxSize)) {
42749
+ return false;
42750
+ }
42751
+ const duration = request.duration || 0;
42752
+ const minDuration = parseThreshold(filter.advanced.minDuration);
42753
+ if (minDuration !== null && duration < minDuration) {
42754
+ return false;
42755
+ }
42756
+ const maxDuration = parseThreshold(filter.advanced.maxDuration);
42757
+ if (maxDuration !== null && duration > maxDuration) {
42758
+ return false;
42759
+ }
42760
+ const searchText = filter.text.trim().toLowerCase();
42761
+ if (!searchText) {
42762
+ return true;
42763
+ }
42764
+ const searchableFields = [
42765
+ request.name,
42766
+ request.method,
42767
+ request.status,
42768
+ request.httpStatus,
42769
+ request.source,
42770
+ request.type,
42771
+ request.contentType,
42772
+ domain,
42773
+ path
42774
+ ].filter((value) => value !== void 0 && value !== null).join(" ").toLowerCase();
42775
+ return searchableFields.includes(searchText);
42776
+ };
42080
42777
  const InspectorView = ({ client: client2 }) => {
42081
42778
  const actions = useNetworkActivityActions();
42082
42779
  const clientManagement = useNetworkActivityClientManagement();
42083
42780
  const hasSelectedRequest = useHasSelectedRequest();
42084
42781
  const overrides = useOverrides();
42782
+ const processedRequests = useProcessedRequests();
42085
42783
  const [filter, setFilter] = reactExports.useState(
42086
42784
  () => createDefaultFilter()
42087
42785
  );
42786
+ const [timelineSelection, setTimelineSelection] = reactExports.useState(null);
42787
+ const filteredRequests = reactExports.useMemo(() => {
42788
+ return processedRequests.filter(
42789
+ (request) => matchesRequestFilter(request, filter, {
42790
+ hasOverride: overrides.has(request.name)
42791
+ })
42792
+ );
42793
+ }, [processedRequests, filter, overrides]);
42794
+ const visibleRequests = reactExports.useMemo(() => {
42795
+ if (!timelineSelection) {
42796
+ return filteredRequests;
42797
+ }
42798
+ const now = Date.now();
42799
+ return filteredRequests.filter(
42800
+ (request) => requestOverlapsTimelineRange(request, timelineSelection, now)
42801
+ );
42802
+ }, [filteredRequests, timelineSelection]);
42088
42803
  reactExports.useEffect(() => {
42089
42804
  if (!client2) {
42090
42805
  return;
@@ -42103,11 +42818,22 @@ const InspectorView = ({ client: client2 }) => {
42103
42818
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, {}),
42104
42819
  /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar, { filter, onFilterChange: setFilter }),
42105
42820
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
42106
- /* @__PURE__ */ jsxRuntimeExports.jsx(
42821
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
42107
42822
  "div",
42108
42823
  {
42109
42824
  className: `flex flex-col ${hasSelectedRequest ? "w-1/2" : "w-full"} border-r border-gray-700 overflow-hidden`,
42110
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(RequestList, { filter })
42825
+ children: [
42826
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42827
+ NetworkTimeline,
42828
+ {
42829
+ requests: filteredRequests,
42830
+ selection: timelineSelection,
42831
+ filteredRequestCount: visibleRequests.length,
42832
+ onSelectionChange: setTimelineSelection
42833
+ }
42834
+ ),
42835
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RequestList, { requests: visibleRequests })
42836
+ ]
42111
42837
  }
42112
42838
  ),
42113
42839
  hasSelectedRequest && /* @__PURE__ */ jsxRuntimeExports.jsx(SidePanel, {})