@rozenite/network-activity-plugin 1.10.0 → 1.12.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.
@@ -20836,12 +20836,13 @@ const getGeneratedFrameLocation$1 = (frame) => ({
20836
20836
  columnNumber: frame.generatedColumnNumber ?? frame.columnNumber
20837
20837
  });
20838
20838
  const isGeneratedBundleUrl = (url) => /[^/]+\.bundle(?:[/?#]|$)/.test(url);
20839
+ const isMetroSymbolicatableUrl = (url) => url?.startsWith("http") ?? false;
20839
20840
  const canSymbolicateStack = (stack) => stack?.some(
20840
- (frame) => getGeneratedFrameLocation$1(frame).url?.startsWith("http")
20841
+ (frame) => isMetroSymbolicatableUrl(getGeneratedFrameLocation$1(frame).url)
20841
20842
  ) ?? false;
20842
20843
  const toReactNativeStackFrame = (frame) => {
20843
20844
  const generatedLocation = getGeneratedFrameLocation$1(frame);
20844
- if (!generatedLocation.url) {
20845
+ if (!isMetroSymbolicatableUrl(generatedLocation.url)) {
20845
20846
  return null;
20846
20847
  }
20847
20848
  return {
@@ -20934,14 +20935,36 @@ const symbolicateInitiator = async (initiator, symbolicateStackTrace = symbolica
20934
20935
  if (!canSymbolicateStack(initiator.stack)) {
20935
20936
  return null;
20936
20937
  }
20937
- const generatedStackFrames = initiator.stack?.map(toReactNativeStackFrame).filter((frame) => frame !== null) ?? [];
20938
+ const originalStack = initiator.stack ?? [];
20939
+ const generatedStackFrames = originalStack.flatMap(
20940
+ (originalFrame, originalIndex) => {
20941
+ const frame = toReactNativeStackFrame(originalFrame);
20942
+ return frame ? [{ frame, originalIndex }] : [];
20943
+ }
20944
+ );
20938
20945
  if (generatedStackFrames.length === 0) {
20939
20946
  return null;
20940
20947
  }
20941
20948
  try {
20942
- const symbolicatedStackTrace = await symbolicateStackTrace(generatedStackFrames);
20943
- const symbolicatedStack = symbolicatedStackTrace.stack.map(
20944
- (frame, index2) => fromSymbolicatedStackFrame(frame, initiator.stack?.[index2])
20949
+ const symbolicatedStackTrace = await symbolicateStackTrace(
20950
+ generatedStackFrames.map((entry) => entry.frame)
20951
+ );
20952
+ const symbolicatedFramesByOriginalIndex = /* @__PURE__ */ new Map();
20953
+ symbolicatedStackTrace.stack.forEach((frame, index2) => {
20954
+ const generatedFrame = generatedStackFrames[index2];
20955
+ if (!generatedFrame) {
20956
+ return;
20957
+ }
20958
+ symbolicatedFramesByOriginalIndex.set(
20959
+ generatedFrame.originalIndex,
20960
+ fromSymbolicatedStackFrame(
20961
+ frame,
20962
+ originalStack[generatedFrame.originalIndex]
20963
+ )
20964
+ );
20965
+ });
20966
+ const symbolicatedStack = originalStack.map(
20967
+ (frame, index2) => symbolicatedFramesByOriginalIndex.get(index2) ?? frame
20945
20968
  );
20946
20969
  const sourceFrame = getPreferredSourceFrame(
20947
20970
  symbolicatedStack,
@@ -20975,6 +20998,9 @@ const symbolicateInitiator = async (initiator, symbolicateStackTrace = symbolica
20975
20998
  }
20976
20999
  };
20977
21000
  const STORE_VERSION = 1;
21001
+ const getElapsedDuration = (endTimestamp, startTimestamp) => {
21002
+ return Math.max(endTimestamp - startTimestamp, 0);
21003
+ };
20978
21004
  const createNetworkActivityStore = () => createStore()(
20979
21005
  persist(
20980
21006
  (set, get) => ({
@@ -21169,6 +21195,10 @@ const createNetworkActivityStore = () => createStore()(
21169
21195
  const updatedEntry = {
21170
21196
  ...httpEntry,
21171
21197
  status: "failed",
21198
+ duration: getElapsedDuration(
21199
+ eventData.timestamp,
21200
+ httpEntry.timestamp
21201
+ ),
21172
21202
  error: eventData.error
21173
21203
  };
21174
21204
  const newEntries = new Map(state.networkEntries);
@@ -21259,7 +21289,10 @@ const createNetworkActivityStore = () => createStore()(
21259
21289
  status: "closed",
21260
21290
  closeCode: eventData.code,
21261
21291
  closeReason: eventData.reason,
21262
- duration: eventData.timestamp - wsEntry.timestamp
21292
+ duration: getElapsedDuration(
21293
+ eventData.timestamp,
21294
+ wsEntry.timestamp
21295
+ )
21263
21296
  };
21264
21297
  const newEntries = new Map(state.networkEntries);
21265
21298
  newEntries.set(entry.id, updatedEntry);
@@ -21324,6 +21357,10 @@ const createNetworkActivityStore = () => createStore()(
21324
21357
  const updatedEntry = {
21325
21358
  ...wsEntry,
21326
21359
  status: "error",
21360
+ duration: getElapsedDuration(
21361
+ eventData.timestamp,
21362
+ wsEntry.timestamp
21363
+ ),
21327
21364
  error: eventData.error
21328
21365
  };
21329
21366
  const newEntries = new Map(state.networkEntries);
@@ -21408,6 +21445,10 @@ const createNetworkActivityStore = () => createStore()(
21408
21445
  const updatedEntry = {
21409
21446
  ...sseEntry,
21410
21447
  status: "error",
21448
+ duration: getElapsedDuration(
21449
+ eventData.timestamp,
21450
+ sseEntry.timestamp
21451
+ ),
21411
21452
  error: eventData.error.message
21412
21453
  };
21413
21454
  const newEntries = new Map(state.networkEntries);
@@ -21425,7 +21466,10 @@ const createNetworkActivityStore = () => createStore()(
21425
21466
  const updatedEntry = {
21426
21467
  ...sseEntry,
21427
21468
  status: "closed",
21428
- duration: eventData.timestamp - sseEntry.timestamp
21469
+ duration: getElapsedDuration(
21470
+ eventData.timestamp,
21471
+ sseEntry.timestamp
21472
+ )
21429
21473
  };
21430
21474
  const newEntries = new Map(state.networkEntries);
21431
21475
  newEntries.set(eventData.requestId, updatedEntry);
@@ -21862,6 +21906,7 @@ const getProcessedRequests = memoize((state) => {
21862
21906
  method: httpEntry.request.method,
21863
21907
  httpStatus: httpEntry.response?.status,
21864
21908
  contentType: httpEntry.response?.contentType,
21909
+ ttfb: httpEntry.ttfb,
21865
21910
  progress: httpEntry.progress
21866
21911
  });
21867
21912
  } else if (entry.type === "websocket") {
@@ -21939,9 +21984,232 @@ const useOverrides = () => {
21939
21984
  const useClientUISettings = () => {
21940
21985
  return useNetworkActivityStore((state) => state.clientUISettings);
21941
21986
  };
21987
+ const base64ToBytes = (base64) => {
21988
+ const binary = atob(base64);
21989
+ const bytes = new Uint8Array(binary.length);
21990
+ for (let i = 0; i < binary.length; i++) {
21991
+ bytes[i] = binary.charCodeAt(i);
21992
+ }
21993
+ return bytes;
21994
+ };
21995
+ const base64ToBlob = (base64, contentType) => {
21996
+ const binary = atob(base64);
21997
+ const buffer = new ArrayBuffer(binary.length);
21998
+ const view = new Uint8Array(buffer);
21999
+ for (let i = 0; i < binary.length; i++) {
22000
+ view[i] = binary.charCodeAt(i);
22001
+ }
22002
+ return new Blob([buffer], {
22003
+ type: contentType || "application/octet-stream"
22004
+ });
22005
+ };
22006
+ const CONTENT_TYPE_EXTENSIONS = {
22007
+ "application/pdf": "pdf",
22008
+ "application/zip": "zip",
22009
+ "application/gzip": "gz",
22010
+ "application/json": "json",
22011
+ "application/xml": "xml",
22012
+ "application/javascript": "js",
22013
+ "application/octet-stream": "bin",
22014
+ "image/png": "png",
22015
+ "image/jpeg": "jpg",
22016
+ "image/gif": "gif",
22017
+ "image/webp": "webp",
22018
+ "image/svg+xml": "svg",
22019
+ "image/bmp": "bmp",
22020
+ "image/x-icon": "ico",
22021
+ "audio/mpeg": "mp3",
22022
+ "audio/ogg": "ogg",
22023
+ "audio/wav": "wav",
22024
+ "video/mp4": "mp4",
22025
+ "video/webm": "webm",
22026
+ "font/woff": "woff",
22027
+ "font/woff2": "woff2",
22028
+ "font/ttf": "ttf",
22029
+ "font/otf": "otf",
22030
+ "text/html": "html",
22031
+ "text/plain": "txt",
22032
+ "text/css": "css",
22033
+ "text/csv": "csv"
22034
+ };
22035
+ const extensionForContentType = (contentType) => {
22036
+ const bare = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
22037
+ return CONTENT_TYPE_EXTENSIONS[bare] ?? "bin";
22038
+ };
22039
+ const readHeader = (headers, name) => {
22040
+ if (!headers) return void 0;
22041
+ const lowerTarget = name.toLowerCase();
22042
+ for (const [key, value] of Object.entries(headers)) {
22043
+ if (key.toLowerCase() === lowerTarget) {
22044
+ return Array.isArray(value) ? value[0] : value;
22045
+ }
22046
+ }
22047
+ return void 0;
22048
+ };
22049
+ const parseContentDispositionFilename = (header) => {
22050
+ if (!header) return void 0;
22051
+ const extended = /filename\*\s*=\s*[^']*''([^;]+)/i.exec(header);
22052
+ if (extended?.[1]) {
22053
+ try {
22054
+ return decodeURIComponent(extended[1].trim()) || void 0;
22055
+ } catch {
22056
+ }
22057
+ }
22058
+ const basic2 = /filename\s*=\s*("([^"]*)"|([^;]+))/i.exec(header);
22059
+ const value = basic2?.[2] ?? basic2?.[3];
22060
+ return value?.trim() || void 0;
22061
+ };
22062
+ const filenameFromUrl = (url) => {
22063
+ try {
22064
+ const parsed = new URL(url);
22065
+ const segments = parsed.pathname.split("/").filter(Boolean);
22066
+ const last = segments[segments.length - 1];
22067
+ return last && last.length > 0 ? last : void 0;
22068
+ } catch {
22069
+ return void 0;
22070
+ }
22071
+ };
22072
+ const deriveFilename = ({
22073
+ headers,
22074
+ url,
22075
+ contentType
22076
+ }) => {
22077
+ const fromDisposition = parseContentDispositionFilename(
22078
+ readHeader(headers, "Content-Disposition")
22079
+ );
22080
+ if (fromDisposition) return fromDisposition;
22081
+ const fromUrl = filenameFromUrl(url);
22082
+ if (fromUrl) return fromUrl;
22083
+ return `response.${extensionForContentType(contentType)}`;
22084
+ };
22085
+ const downloadBlob = (blob, filename) => {
22086
+ const objectUrl = URL.createObjectURL(blob);
22087
+ const anchor = document.createElement("a");
22088
+ anchor.href = objectUrl;
22089
+ anchor.download = filename;
22090
+ document.body.appendChild(anchor);
22091
+ anchor.click();
22092
+ document.body.removeChild(anchor);
22093
+ setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
22094
+ };
22095
+ const downloadJson = (data, filename) => {
22096
+ downloadBlob(
22097
+ new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }),
22098
+ filename
22099
+ );
22100
+ };
22101
+ const EXPORT_SCHEMA_VERSION = 1;
22102
+ const getDuration = (duration) => duration ?? null;
22103
+ const serializeHttpEntry = (entry) => ({
22104
+ id: entry.id,
22105
+ type: "http",
22106
+ source: entry.source,
22107
+ timestamp: entry.timestamp,
22108
+ duration: getDuration(entry.duration),
22109
+ status: entry.status,
22110
+ error: entry.error,
22111
+ canceled: entry.canceled,
22112
+ request: entry.request,
22113
+ response: entry.response ?? null,
22114
+ size: entry.size ?? null,
22115
+ ttfb: entry.ttfb ?? null,
22116
+ initiator: entry.initiator,
22117
+ resourceType: entry.resourceType,
22118
+ progress: entry.progress
22119
+ });
22120
+ const serializeWebSocketEntry = (entry, websocketMessages) => ({
22121
+ id: entry.id,
22122
+ type: "websocket",
22123
+ source: entry.source,
22124
+ timestamp: entry.timestamp,
22125
+ duration: getDuration(entry.duration),
22126
+ status: entry.status,
22127
+ connection: entry.connection,
22128
+ error: entry.error,
22129
+ closeCode: entry.closeCode,
22130
+ closeReason: entry.closeReason,
22131
+ messages: websocketMessages.get(entry.id) ?? []
22132
+ });
22133
+ const serializeSSEEntry = (entry) => ({
22134
+ id: entry.id,
22135
+ type: "sse",
22136
+ source: entry.source,
22137
+ timestamp: entry.timestamp,
22138
+ duration: getDuration(entry.duration),
22139
+ status: entry.status,
22140
+ error: entry.error,
22141
+ request: entry.request,
22142
+ response: entry.response ?? null,
22143
+ initiator: entry.initiator,
22144
+ resourceType: entry.resourceType,
22145
+ messages: entry.messages
22146
+ });
22147
+ const serializeEntry = (entry, websocketMessages) => {
22148
+ switch (entry.type) {
22149
+ case "http":
22150
+ return serializeHttpEntry(entry);
22151
+ case "websocket":
22152
+ return serializeWebSocketEntry(entry, websocketMessages);
22153
+ case "sse":
22154
+ return serializeSSEEntry(entry);
22155
+ }
22156
+ };
22157
+ const createNetworkActivitySessionExport = (networkEntries, websocketMessages, exportedAt = /* @__PURE__ */ new Date()) => {
22158
+ const entries = Array.from(networkEntries.values()).sort((a, b2) => a.timestamp - b2.timestamp).map((entry) => serializeEntry(entry, websocketMessages));
22159
+ return {
22160
+ schemaVersion: EXPORT_SCHEMA_VERSION,
22161
+ tool: "rozenite-network-activity",
22162
+ exportedAt: exportedAt.toISOString(),
22163
+ summary: {
22164
+ totalEntries: entries.length,
22165
+ httpRequests: entries.filter((entry) => entry.type === "http").length,
22166
+ webSocketConnections: entries.filter(
22167
+ (entry) => entry.type === "websocket"
22168
+ ).length,
22169
+ sseConnections: entries.filter((entry) => entry.type === "sse").length,
22170
+ realtimeMessages: entries.reduce((count2, entry) => {
22171
+ if (entry.type === "websocket" || entry.type === "sse") {
22172
+ return count2 + entry.messages.length;
22173
+ }
22174
+ return count2;
22175
+ }, 0)
22176
+ },
22177
+ entries
22178
+ };
22179
+ };
22180
+ const getNetworkActivitySessionExportFileName = (exportedAt = /* @__PURE__ */ new Date()) => {
22181
+ const timestamp = exportedAt.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:]/g, "-");
22182
+ return `rozenite-network-session-${timestamp}.json`;
22183
+ };
22184
+ const useNetworkActivitySessionExport = () => {
22185
+ const canExportSession = useNetworkActivityStore(
22186
+ (state) => state.networkEntries.size > 0
22187
+ );
22188
+ const exportSession = reactExports.useCallback(() => {
22189
+ const { networkEntries, websocketMessages } = store.getState();
22190
+ if (networkEntries.size === 0) {
22191
+ return;
22192
+ }
22193
+ const exportedAt = /* @__PURE__ */ new Date();
22194
+ const exportData = createNetworkActivitySessionExport(
22195
+ networkEntries,
22196
+ websocketMessages,
22197
+ exportedAt
22198
+ );
22199
+ downloadJson(
22200
+ exportData,
22201
+ getNetworkActivitySessionExportFileName(exportedAt)
22202
+ );
22203
+ }, []);
22204
+ return {
22205
+ canExportSession,
22206
+ exportSession
22207
+ };
22208
+ };
21942
22209
  const Toolbar = () => {
21943
22210
  const actions = useNetworkActivityActions();
21944
22211
  const isRecording = useIsRecording();
22212
+ const { canExportSession, exportSession } = useNetworkActivitySessionExport();
21945
22213
  const onToggleRecording = () => {
21946
22214
  actions.setRecording(!isRecording);
21947
22215
  };
@@ -21969,6 +22237,18 @@ const Toolbar = () => {
21969
22237
  className: "h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
21970
22238
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "h-4 w-4" })
21971
22239
  }
22240
+ ),
22241
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
22242
+ Button,
22243
+ {
22244
+ variant: "ghost",
22245
+ size: "sm",
22246
+ onClick: exportSession,
22247
+ disabled: !canExportSession,
22248
+ className: "ml-auto h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
22249
+ title: "Export session",
22250
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "h-4 w-4" })
22251
+ }
21972
22252
  )
21973
22253
  ] });
21974
22254
  };
@@ -24755,7 +25035,7 @@ const formatStartTime = (startTime) => {
24755
25035
  const milliseconds = date.getMilliseconds().toString().padStart(3, "0");
24756
25036
  return `${timeString}.${milliseconds}`;
24757
25037
  };
24758
- const extractDomainAndPath = (url) => {
25038
+ const extractDomainAndPath$1 = (url) => {
24759
25039
  try {
24760
25040
  const { hostname, pathname, search, hash: hash2, port } = new URL(url);
24761
25041
  return {
@@ -24806,122 +25086,9 @@ const sortTime = (rowA, rowB, columnId) => {
24806
25086
  };
24807
25087
  return getNumericValue(a) - getNumericValue(b2);
24808
25088
  };
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
25089
  const processNetworkRequests = (processedRequests, overrides, showEntirePathAsName = false) => {
24923
25090
  return processedRequests.map((request) => {
24924
- const { domain, path } = extractDomainAndPath(request.name);
25091
+ const { domain, path } = extractDomainAndPath$1(request.name);
24925
25092
  const duration = request.duration || 0;
24926
25093
  const hasOverride = overrides.has(request.name);
24927
25094
  let statusDisplay = request.httpStatus || request.status;
@@ -24936,7 +25103,6 @@ const processNetworkRequests = (processedRequests, overrides, showEntirePathAsNa
24936
25103
  name: generateName(request.name, showEntirePathAsName),
24937
25104
  status: statusDisplay,
24938
25105
  statusCode: request.httpStatus || void 0,
24939
- statusState: request.status,
24940
25106
  method: request.method,
24941
25107
  domain,
24942
25108
  path,
@@ -25002,21 +25168,19 @@ const columns$1 = [
25002
25168
  sortingFn: sortTime
25003
25169
  })
25004
25170
  ];
25005
- const RequestList = ({ filter }) => {
25171
+ const RequestList = ({ requests: filteredRequests }) => {
25006
25172
  const actions = useNetworkActivityActions();
25007
- const processedRequests = useProcessedRequests();
25008
25173
  const selectedRequestId = useSelectedRequestId();
25009
25174
  const [sorting, setSorting] = reactExports.useState([]);
25010
25175
  const overrides = useOverrides();
25011
25176
  const clientUISettings = useClientUISettings();
25012
25177
  const requests = reactExports.useMemo(() => {
25013
- const allRequests = processNetworkRequests(
25014
- processedRequests,
25178
+ return processNetworkRequests(
25179
+ filteredRequests,
25015
25180
  overrides,
25016
25181
  clientUISettings?.showUrlAsName
25017
25182
  );
25018
- return filterNetworkRequests(allRequests, filter);
25019
- }, [processedRequests, overrides, clientUISettings?.showUrlAsName, filter]);
25183
+ }, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
25020
25184
  const table = useReactTable({
25021
25185
  data: requests,
25022
25186
  columns: columns$1,
@@ -25879,7 +26043,7 @@ const TabsContent = reactExports.forwardRef(({ className, ...props }, ref) => /*
25879
26043
  }
25880
26044
  ));
25881
26045
  TabsContent.displayName = Content$1.displayName;
25882
- function clamp$1(value, [min2, max2]) {
26046
+ function clamp$2(value, [min2, max2]) {
25883
26047
  return Math.min(max2, Math.max(min2, value));
25884
26048
  }
25885
26049
  function useStateMachine(initialState, machine) {
@@ -26529,7 +26693,7 @@ function getThumbOffsetFromScroll(scrollPos, sizes, dir = "ltr") {
26529
26693
  const maxScrollPos = sizes.content - sizes.viewport;
26530
26694
  const maxThumbPos = scrollbar - thumbSizePx;
26531
26695
  const scrollClampRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0];
26532
- const scrollWithoutMomentum = clamp$1(scrollPos, scrollClampRange);
26696
+ const scrollWithoutMomentum = clamp$2(scrollPos, scrollClampRange);
26533
26697
  const interpolate = linearScale([0, maxScrollPos], [0, maxThumbPos]);
26534
26698
  return interpolate(scrollWithoutMomentum);
26535
26699
  }
@@ -27272,7 +27436,7 @@ const oppositeAlignmentMap = {
27272
27436
  start: "end",
27273
27437
  end: "start"
27274
27438
  };
27275
- function clamp(start, value, end) {
27439
+ function clamp$1(start, value, end) {
27276
27440
  return max(start, min(value, end));
27277
27441
  }
27278
27442
  function evaluate(value, param) {
@@ -27623,7 +27787,7 @@ const arrow$3 = (options) => ({
27623
27787
  const min$12 = minPadding;
27624
27788
  const max2 = clientSize - arrowDimensions[length] - maxPadding;
27625
27789
  const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
27626
- const offset2 = clamp(min$12, center, max2);
27790
+ const offset2 = clamp$1(min$12, center, max2);
27627
27791
  const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset2 && rects.reference[length] / 2 - (center < min$12 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
27628
27792
  const alignmentOffset = shouldAddOffset ? center < min$12 ? center - min$12 : center - max2 : 0;
27629
27793
  return {
@@ -27920,14 +28084,14 @@ const shift$2 = function(options) {
27920
28084
  const maxSide = mainAxis === "y" ? "bottom" : "right";
27921
28085
  const min2 = mainAxisCoord + overflow[minSide];
27922
28086
  const max2 = mainAxisCoord - overflow[maxSide];
27923
- mainAxisCoord = clamp(min2, mainAxisCoord, max2);
28087
+ mainAxisCoord = clamp$1(min2, mainAxisCoord, max2);
27924
28088
  }
27925
28089
  if (checkCrossAxis) {
27926
28090
  const minSide = crossAxis === "y" ? "top" : "left";
27927
28091
  const maxSide = crossAxis === "y" ? "bottom" : "right";
27928
28092
  const min2 = crossAxisCoord + overflow[minSide];
27929
28093
  const max2 = crossAxisCoord - overflow[maxSide];
27930
- crossAxisCoord = clamp(min2, crossAxisCoord, max2);
28094
+ crossAxisCoord = clamp$1(min2, crossAxisCoord, max2);
27931
28095
  }
27932
28096
  const limitedCoords = limiter.fn({
27933
28097
  ...state,
@@ -38796,120 +38960,12 @@ const HexView = ({ bytes }) => {
38796
38960
  }
38797
38961
  ) });
38798
38962
  };
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);
38963
+ const formatBytes$1 = (bytes) => {
38964
+ if (bytes >= 1024 * 1024) {
38965
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
38804
38966
  }
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);
38813
- }
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`;
38967
+ if (bytes >= 1024) {
38968
+ return `${(bytes / 1024).toFixed(1)} KB`;
38913
38969
  }
38914
38970
  return `${bytes} bytes`;
38915
38971
  };
@@ -41730,19 +41786,14 @@ const Input = reactExports.forwardRef(
41730
41786
  }
41731
41787
  );
41732
41788
  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"
41789
+ const DEFAULT_REQUEST_TYPES = [
41790
+ "http",
41791
+ "websocket",
41792
+ "sse"
41741
41793
  ];
41742
- const SOURCES = ["builtin", "nitro"];
41743
41794
  const createDefaultFilter = () => ({
41744
41795
  text: "",
41745
- types: /* @__PURE__ */ new Set(),
41796
+ types: new Set(DEFAULT_REQUEST_TYPES),
41746
41797
  advanced: {
41747
41798
  methods: /* @__PURE__ */ new Set(),
41748
41799
  sources: /* @__PURE__ */ new Set(),
@@ -41758,6 +41809,15 @@ const createDefaultFilter = () => ({
41758
41809
  maxDuration: ""
41759
41810
  }
41760
41811
  });
41812
+ const HTTP_METHODS = [
41813
+ "GET",
41814
+ "POST",
41815
+ "PUT",
41816
+ "PATCH",
41817
+ "DELETE",
41818
+ "HEAD"
41819
+ ];
41820
+ const SOURCES = ["builtin", "nitro"];
41761
41821
  const getTypeLabel = (type) => {
41762
41822
  switch (type) {
41763
41823
  case "http":
@@ -41793,7 +41853,7 @@ const getAdvancedFilterCount = (advanced) => {
41793
41853
  ].filter(Boolean).length;
41794
41854
  };
41795
41855
  const getActiveFilterCount = (filter) => {
41796
- const typeFilterCount = filter.types.size > 0 ? 1 : 0;
41856
+ const typeFilterCount = filter.types.size < DEFAULT_REQUEST_TYPES.length ? 1 : 0;
41797
41857
  return typeFilterCount + getAdvancedFilterCount(filter.advanced);
41798
41858
  };
41799
41859
  const FilterField = ({
@@ -41904,7 +41964,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
41904
41964
  onFilterChange(createDefaultFilter());
41905
41965
  };
41906
41966
  const activeFilterCount = getActiveFilterCount(filter);
41907
- const hasActiveFilters = filter.text !== "" || activeFilterCount > 0;
41967
+ const hasActiveFilters = filter.text.trim() !== "" || activeFilterCount > 0;
41908
41968
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 p-2 border-b border-gray-700 bg-gray-800", children: [
41909
41969
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
41910
41970
  Input,
@@ -41940,7 +42000,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
41940
42000
  ...getFloatingProps(),
41941
42001
  children: [
41942
42002
  /* @__PURE__ */ jsxRuntimeExports.jsx(FilterPanelLabel, { children: "Request Type" }),
41943
- REQUEST_TYPES.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42003
+ DEFAULT_REQUEST_TYPES.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
41944
42004
  FilterCheckbox,
41945
42005
  {
41946
42006
  checked: filter.types.has(type),
@@ -42077,14 +42137,692 @@ const FilterBar = ({ filter, onFilterChange }) => {
42077
42137
  )
42078
42138
  ] });
42079
42139
  };
42140
+ const TIMELINE_LAYOUT = {
42141
+ minVisibleBarPercent: 0.65,
42142
+ minRangeMs: 1e3,
42143
+ liveRefreshMs: 1e3,
42144
+ maxRenderedRequests: 1e3,
42145
+ laneCount: 8,
42146
+ laneHeightPx: 2,
42147
+ laneGapPx: 6,
42148
+ laneHitTargetHeightPx: 8,
42149
+ rulerHeightPx: 22,
42150
+ laneTopPx: 32,
42151
+ laneBottomPaddingPx: 18,
42152
+ tickTargetCount: 7,
42153
+ minTickLabelGapPercent: 6,
42154
+ rangePaddingRatio: 0.025,
42155
+ minRangePaddingMs: 25,
42156
+ streamingRequestMaxDurationMs: 5e3
42157
+ };
42158
+ const NICE_TICK_FACTORS = [1, 2, 2.5, 5, 10];
42159
+ const ACTIVE_HTTP_STATUSES = /* @__PURE__ */ new Set([
42160
+ "pending",
42161
+ "loading"
42162
+ ]);
42163
+ const ACTIVE_WEBSOCKET_STATUSES = /* @__PURE__ */ new Set([
42164
+ "connecting",
42165
+ "open",
42166
+ "closing"
42167
+ ]);
42168
+ const ACTIVE_SSE_STATUSES = /* @__PURE__ */ new Set([
42169
+ "connecting",
42170
+ "open"
42171
+ ]);
42172
+ const clamp = (value, minimum, maximum) => {
42173
+ return Math.min(Math.max(value, minimum), maximum);
42174
+ };
42175
+ const getTimelineChartHeight = (layout = TIMELINE_LAYOUT) => {
42176
+ return layout.laneTopPx + layout.laneCount * layout.laneHeightPx + (layout.laneCount - 1) * layout.laneGapPx + layout.laneBottomPaddingPx;
42177
+ };
42178
+ const getTimelineLaneTop = (lane, layout = TIMELINE_LAYOUT) => {
42179
+ return lane * (layout.laneHeightPx + layout.laneGapPx) + layout.laneTopPx;
42180
+ };
42181
+ const getTimelineTrackTop = (lane, layout = TIMELINE_LAYOUT) => {
42182
+ const visualBarTop = getTimelineLaneTop(lane, layout);
42183
+ return visualBarTop - (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
42184
+ };
42185
+ const getTimelineBarTopOffset = (layout = TIMELINE_LAYOUT) => {
42186
+ return (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
42187
+ };
42188
+ const isRequestActive = (request) => {
42189
+ switch (request.type) {
42190
+ case "http":
42191
+ return ACTIVE_HTTP_STATUSES.has(request.status);
42192
+ case "websocket":
42193
+ return ACTIVE_WEBSOCKET_STATUSES.has(request.status);
42194
+ case "sse":
42195
+ return ACTIVE_SSE_STATUSES.has(request.status);
42196
+ }
42197
+ };
42198
+ const formatTimelineOffset = (milliseconds) => {
42199
+ if (milliseconds < 1e3) {
42200
+ return `${Math.round(milliseconds)} ms`;
42201
+ }
42202
+ if (milliseconds < 6e4) {
42203
+ return `${(milliseconds / 1e3).toFixed(milliseconds < 1e4 ? 1 : 0)} s`;
42204
+ }
42205
+ const totalSeconds = Math.round(milliseconds / 1e3);
42206
+ const minutes = Math.floor(totalSeconds / 60);
42207
+ const seconds = totalSeconds % 60;
42208
+ return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
42209
+ };
42210
+ const getRequestEndTime = (request, now) => {
42211
+ if (typeof request.duration === "number") {
42212
+ return request.timestamp + Math.max(request.duration, 0);
42213
+ }
42214
+ if (isRequestActive(request)) {
42215
+ return Math.max(now, request.timestamp);
42216
+ }
42217
+ return request.timestamp;
42218
+ };
42219
+ const getTimelineRequestEndTime = (request, now, layout = TIMELINE_LAYOUT) => {
42220
+ const endTime = getRequestEndTime(request, now);
42221
+ if (request.type !== "websocket" && request.type !== "sse") {
42222
+ return endTime;
42223
+ }
42224
+ return Math.min(
42225
+ endTime,
42226
+ request.timestamp + layout.streamingRequestMaxDurationMs
42227
+ );
42228
+ };
42229
+ const requestOverlapsTimelineRange = (request, range, now, layout = TIMELINE_LAYOUT) => {
42230
+ const rangeStart = Math.min(range.startTime, range.endTime);
42231
+ const rangeEnd = Math.max(range.startTime, range.endTime);
42232
+ const requestStart = request.timestamp;
42233
+ const requestEnd = getTimelineRequestEndTime(request, now, layout);
42234
+ return requestStart <= rangeEnd && requestEnd >= rangeStart;
42235
+ };
42236
+ const getNiceTickStep = (rangeDuration, targetTickCount) => {
42237
+ const targetStep = rangeDuration / targetTickCount;
42238
+ const exponent = Math.floor(Math.log10(targetStep));
42239
+ const magnitude = 10 ** exponent;
42240
+ const normalizedStep = targetStep / magnitude;
42241
+ const factor = NICE_TICK_FACTORS.find((candidate) => candidate >= normalizedStep) ?? NICE_TICK_FACTORS[NICE_TICK_FACTORS.length - 1];
42242
+ return factor * magnitude;
42243
+ };
42244
+ const getTimelineTicks = (rangeDuration, layout = TIMELINE_LAYOUT) => {
42245
+ const step = getNiceTickStep(rangeDuration, layout.tickTargetCount);
42246
+ const ticks = [];
42247
+ for (let value = 0; value <= rangeDuration; value += step) {
42248
+ ticks.push({
42249
+ label: formatTimelineOffset(value),
42250
+ offsetPercent: value / rangeDuration * 100
42251
+ });
42252
+ }
42253
+ if (ticks.length === 0 || ticks[ticks.length - 1].offsetPercent < 100 - Number.EPSILON) {
42254
+ const finalTick = {
42255
+ label: formatTimelineOffset(rangeDuration),
42256
+ offsetPercent: 100
42257
+ };
42258
+ const previousTick = ticks[ticks.length - 1];
42259
+ if (!previousTick || finalTick.label !== previousTick.label && finalTick.offsetPercent - previousTick.offsetPercent >= layout.minTickLabelGapPercent) {
42260
+ ticks.push(finalTick);
42261
+ }
42262
+ }
42263
+ return ticks;
42264
+ };
42265
+ const getTimelineBounds = (requests, now, layout) => {
42266
+ return requests.reduce(
42267
+ (result, request) => {
42268
+ const endTime = getTimelineRequestEndTime(request, now, layout);
42269
+ return {
42270
+ start: Math.min(result.start, request.timestamp),
42271
+ end: Math.max(result.end, endTime)
42272
+ };
42273
+ },
42274
+ {
42275
+ start: Number.POSITIVE_INFINITY,
42276
+ end: Number.NEGATIVE_INFINITY
42277
+ }
42278
+ );
42279
+ };
42280
+ const getEarliestLaneIndex = (laneEndTimes) => {
42281
+ return laneEndTimes.reduce((earliestIndex, laneEndTime, index2) => {
42282
+ return laneEndTime < laneEndTimes[earliestIndex] ? index2 : earliestIndex;
42283
+ }, 0);
42284
+ };
42285
+ const getRenderableRequests = (requests, layout) => {
42286
+ if (requests.length <= layout.maxRenderedRequests) {
42287
+ return requests;
42288
+ }
42289
+ return [...requests].sort((a, b2) => b2.timestamp - a.timestamp).slice(0, layout.maxRenderedRequests);
42290
+ };
42291
+ const getTimelineModel = (requests, now, layout = TIMELINE_LAYOUT) => {
42292
+ const renderableRequests = getRenderableRequests(requests, layout);
42293
+ const hiddenRequestCount = requests.length - renderableRequests.length;
42294
+ if (renderableRequests.length === 0) {
42295
+ return {
42296
+ rows: [],
42297
+ ticks: getTimelineTicks(layout.minRangeMs, layout),
42298
+ rangeStart: 0,
42299
+ rangeDuration: layout.minRangeMs,
42300
+ chartHeight: getTimelineChartHeight(layout),
42301
+ totalRequestCount: requests.length,
42302
+ hiddenRequestCount
42303
+ };
42304
+ }
42305
+ const bounds = getTimelineBounds(renderableRequests, now, layout);
42306
+ const rawRangeDuration = Math.max(
42307
+ bounds.end - bounds.start,
42308
+ layout.minRangeMs
42309
+ );
42310
+ const rangePadding = Math.max(
42311
+ rawRangeDuration * layout.rangePaddingRatio,
42312
+ layout.minRangePaddingMs
42313
+ );
42314
+ const rangeStart = bounds.start - rangePadding;
42315
+ const rangeDuration = rawRangeDuration + rangePadding * 2;
42316
+ const laneEndTimes = Array.from({ length: layout.laneCount }, () => 0);
42317
+ const rows = [...renderableRequests].sort((a, b2) => a.timestamp - b2.timestamp).map((request) => {
42318
+ const startTime = request.timestamp;
42319
+ const endTime = getTimelineRequestEndTime(request, now, layout);
42320
+ const duration = Math.max(endTime - startTime, 0);
42321
+ const offsetPercent = clamp(
42322
+ (startTime - rangeStart) / rangeDuration * 100,
42323
+ 0,
42324
+ 100 - layout.minVisibleBarPercent
42325
+ );
42326
+ const widthPercent = Math.min(
42327
+ Math.max(duration / rangeDuration * 100, layout.minVisibleBarPercent),
42328
+ 100 - offsetPercent
42329
+ );
42330
+ const ttfb = clamp(request.ttfb ?? 0, 0, duration);
42331
+ const ttfbPercent = duration === 0 ? 0 : ttfb / duration * 100;
42332
+ const receivePercent = Math.max(100 - ttfbPercent, 0);
42333
+ const availableLane = laneEndTimes.findIndex(
42334
+ (laneEndTime) => laneEndTime <= startTime
42335
+ );
42336
+ const isOverflowingLane = availableLane === -1;
42337
+ const lane = isOverflowingLane ? getEarliestLaneIndex(laneEndTimes) : availableLane;
42338
+ laneEndTimes[lane] = Math.max(laneEndTimes[lane], endTime);
42339
+ return {
42340
+ request,
42341
+ offsetPercent,
42342
+ widthPercent,
42343
+ duration,
42344
+ ttfbPercent,
42345
+ receivePercent,
42346
+ isActive: isRequestActive(request),
42347
+ lane,
42348
+ isOverflowingLane
42349
+ };
42350
+ });
42351
+ return {
42352
+ rows,
42353
+ ticks: getTimelineTicks(rangeDuration, layout),
42354
+ rangeStart,
42355
+ rangeDuration,
42356
+ chartHeight: getTimelineChartHeight(layout),
42357
+ totalRequestCount: requests.length,
42358
+ hiddenRequestCount
42359
+ };
42360
+ };
42361
+ const REQUEST_TIMELINE_COLORS = {
42362
+ error: "bg-red-400",
42363
+ primary: "bg-gray-400",
42364
+ active: "bg-gray-500",
42365
+ httpTtfb: "bg-gray-200"
42366
+ };
42367
+ const getPrimaryBarClassName = (request) => {
42368
+ if (request.status === "failed" || request.status === "error") {
42369
+ return REQUEST_TIMELINE_COLORS.error;
42370
+ }
42371
+ return REQUEST_TIMELINE_COLORS.primary;
42372
+ };
42373
+ const getStyle = (offsetPercent, widthPercent) => ({
42374
+ left: `${offsetPercent}%`,
42375
+ width: `${widthPercent}%`
42376
+ });
42377
+ const GridLines = ({ ticks }) => {
42378
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute inset-0", children: ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42379
+ "div",
42380
+ {
42381
+ className: "absolute inset-y-0 border-l border-gray-800",
42382
+ style: { left: `${tick.offsetPercent}%` }
42383
+ },
42384
+ `${tick.label}-${tick.offsetPercent}`
42385
+ )) });
42386
+ };
42387
+ const getTickLabelStyle = (tick) => {
42388
+ if (tick.offsetPercent === 0) {
42389
+ return {
42390
+ left: 4
42391
+ };
42392
+ }
42393
+ if (tick.offsetPercent === 100) {
42394
+ return {
42395
+ right: 4
42396
+ };
42397
+ }
42398
+ return {
42399
+ left: `${tick.offsetPercent}%`
42400
+ };
42401
+ };
42402
+ const TimelineTrack = ({
42403
+ row,
42404
+ isSelected,
42405
+ onSelect,
42406
+ shouldSuppressSelect
42407
+ }) => {
42408
+ const primaryBarClassName = row.isActive ? REQUEST_TIMELINE_COLORS.active : getPrimaryBarClassName(row.request);
42409
+ const isSplitHttpBar = row.request.type === "http" && row.ttfbPercent > 0 && row.receivePercent > 0;
42410
+ const trackTop = getTimelineTrackTop(row.lane);
42411
+ const barTop = getTimelineBarTopOffset();
42412
+ const positionStyle = {
42413
+ ...getStyle(row.offsetPercent, row.widthPercent),
42414
+ top: trackTop
42415
+ };
42416
+ const durationLabel = row.isActive ? `${formatTimelineOffset(row.duration)}+` : formatTimelineOffset(row.duration);
42417
+ const label = `${row.request.method} ${row.request.name} - ${durationLabel}`;
42418
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
42419
+ "button",
42420
+ {
42421
+ type: "button",
42422
+ "aria-label": label,
42423
+ title: label,
42424
+ "data-timeline-track": "true",
42425
+ className: "absolute rounded-sm text-left transition-opacity hover:opacity-80",
42426
+ style: {
42427
+ ...positionStyle,
42428
+ height: TIMELINE_LAYOUT.laneHitTargetHeightPx
42429
+ },
42430
+ onClick: (event) => {
42431
+ if (shouldSuppressSelect()) {
42432
+ event.preventDefault();
42433
+ event.stopPropagation();
42434
+ return;
42435
+ }
42436
+ onSelect(row.request.id);
42437
+ },
42438
+ children: isSplitHttpBar ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
42439
+ "div",
42440
+ {
42441
+ className: `absolute flex w-full overflow-hidden rounded-sm ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
42442
+ style: {
42443
+ top: barTop,
42444
+ height: TIMELINE_LAYOUT.laneHeightPx
42445
+ },
42446
+ children: [
42447
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42448
+ "div",
42449
+ {
42450
+ className: `h-full ${REQUEST_TIMELINE_COLORS.httpTtfb}`,
42451
+ style: { width: `${row.ttfbPercent}%` }
42452
+ }
42453
+ ),
42454
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42455
+ "div",
42456
+ {
42457
+ className: `h-full ${REQUEST_TIMELINE_COLORS.primary}`,
42458
+ style: { width: `${row.receivePercent}%` }
42459
+ }
42460
+ )
42461
+ ]
42462
+ }
42463
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
42464
+ "div",
42465
+ {
42466
+ className: `absolute w-full rounded-sm ${primaryBarClassName} ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
42467
+ style: {
42468
+ top: barTop,
42469
+ height: TIMELINE_LAYOUT.laneHeightPx
42470
+ }
42471
+ }
42472
+ )
42473
+ }
42474
+ );
42475
+ };
42476
+ const clampPercent = (value) => Math.min(Math.max(value, 0), 100);
42477
+ const getPointerPercent = (event, element) => {
42478
+ const rect = element.getBoundingClientRect();
42479
+ if (rect.width === 0) {
42480
+ return 0;
42481
+ }
42482
+ return clampPercent((event.clientX - rect.left) / rect.width * 100);
42483
+ };
42484
+ const getSelectionStyle = (range, timeline) => {
42485
+ const startPercent = clampPercent(
42486
+ (range.startTime - timeline.rangeStart) / timeline.rangeDuration * 100
42487
+ );
42488
+ const endPercent = clampPercent(
42489
+ (range.endTime - timeline.rangeStart) / timeline.rangeDuration * 100
42490
+ );
42491
+ const left2 = Math.min(startPercent, endPercent);
42492
+ const width = Math.abs(endPercent - startPercent);
42493
+ return {
42494
+ left: `${left2}%`,
42495
+ width: `${width}%`,
42496
+ top: TIMELINE_LAYOUT.rulerHeightPx
42497
+ };
42498
+ };
42499
+ const getDraftSelectionStyle = (draft) => {
42500
+ const left2 = Math.min(draft.anchorPercent, draft.currentPercent);
42501
+ const width = Math.abs(draft.currentPercent - draft.anchorPercent);
42502
+ return {
42503
+ left: `${left2}%`,
42504
+ width: `${width}%`,
42505
+ top: TIMELINE_LAYOUT.rulerHeightPx
42506
+ };
42507
+ };
42508
+ const NetworkTimeline = ({
42509
+ requests,
42510
+ selection,
42511
+ filteredRequestCount,
42512
+ onSelectionChange
42513
+ }) => {
42514
+ const actions = useNetworkActivityActions();
42515
+ const selectedRequestId = useSelectedRequestId();
42516
+ const [now, setNow] = reactExports.useState(() => Date.now());
42517
+ const [draftSelection, setDraftSelection] = reactExports.useState(
42518
+ null
42519
+ );
42520
+ const chartRef = reactExports.useRef(null);
42521
+ const suppressTrackClickRef = reactExports.useRef(false);
42522
+ const hasActiveRequests = requests.some(isRequestActive);
42523
+ reactExports.useEffect(() => {
42524
+ if (!hasActiveRequests) {
42525
+ return;
42526
+ }
42527
+ const interval = window.setInterval(() => {
42528
+ setNow(Date.now());
42529
+ }, TIMELINE_LAYOUT.liveRefreshMs);
42530
+ return () => window.clearInterval(interval);
42531
+ }, [hasActiveRequests]);
42532
+ const timeline = reactExports.useMemo(() => {
42533
+ return getTimelineModel(requests, now);
42534
+ }, [requests, now]);
42535
+ const onRequestSelect = (requestId) => {
42536
+ actions.setSelectedRequest(requestId);
42537
+ };
42538
+ const onPointerDown = (event) => {
42539
+ if (event.button !== 0 || requests.length === 0) {
42540
+ return;
42541
+ }
42542
+ const chartElement = chartRef.current;
42543
+ if (!chartElement) {
42544
+ return;
42545
+ }
42546
+ const percent = getPointerPercent(event, chartElement);
42547
+ const target = event.target;
42548
+ const startedOnTrack = target instanceof Element && target.closest('[data-timeline-track="true"]') !== null;
42549
+ setDraftSelection({
42550
+ anchorPercent: percent,
42551
+ currentPercent: percent,
42552
+ startedOnTrack
42553
+ });
42554
+ chartElement.setPointerCapture(event.pointerId);
42555
+ };
42556
+ const onPointerMove = (event) => {
42557
+ if (!draftSelection) {
42558
+ return;
42559
+ }
42560
+ const chartElement = chartRef.current;
42561
+ if (!chartElement) {
42562
+ return;
42563
+ }
42564
+ event.preventDefault();
42565
+ const percent = getPointerPercent(event, chartElement);
42566
+ setDraftSelection(
42567
+ (current) => current ? { ...current, currentPercent: percent } : current
42568
+ );
42569
+ };
42570
+ const onPointerUp = (event) => {
42571
+ if (!draftSelection) {
42572
+ return;
42573
+ }
42574
+ const chartElement = chartRef.current;
42575
+ const currentPercent = chartElement ? getPointerPercent(event, chartElement) : draftSelection.currentPercent;
42576
+ const distance = Math.abs(currentPercent - draftSelection.anchorPercent);
42577
+ if (distance > 1) {
42578
+ const startOffset = Math.min(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
42579
+ const endOffset = Math.max(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
42580
+ onSelectionChange({
42581
+ startTime: timeline.rangeStart + startOffset,
42582
+ endTime: timeline.rangeStart + endOffset
42583
+ });
42584
+ suppressTrackClickRef.current = true;
42585
+ window.setTimeout(() => {
42586
+ suppressTrackClickRef.current = false;
42587
+ }, 0);
42588
+ } else if (!draftSelection.startedOnTrack) {
42589
+ onSelectionChange(null);
42590
+ }
42591
+ setDraftSelection(null);
42592
+ if (chartElement?.hasPointerCapture(event.pointerId)) {
42593
+ chartElement.releasePointerCapture(event.pointerId);
42594
+ }
42595
+ };
42596
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-b border-gray-700 bg-gray-900 p-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
42597
+ "div",
42598
+ {
42599
+ ref: chartRef,
42600
+ className: "relative overflow-hidden border border-gray-800 bg-gray-950",
42601
+ style: { height: timeline.chartHeight },
42602
+ onPointerDown,
42603
+ onPointerMove,
42604
+ onPointerUp,
42605
+ children: [
42606
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GridLines, { ticks: timeline.ticks }),
42607
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42608
+ "div",
42609
+ {
42610
+ className: "pointer-events-none absolute inset-x-0 border-b border-gray-800",
42611
+ style: { top: TIMELINE_LAYOUT.rulerHeightPx }
42612
+ }
42613
+ ),
42614
+ timeline.ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42615
+ "div",
42616
+ {
42617
+ className: "absolute top-1 whitespace-nowrap tabular-nums text-xs text-gray-200",
42618
+ style: getTickLabelStyle(tick),
42619
+ children: tick.label
42620
+ },
42621
+ `${tick.label}-${tick.offsetPercent}`
42622
+ )),
42623
+ selection && /* @__PURE__ */ jsxRuntimeExports.jsx(
42624
+ "div",
42625
+ {
42626
+ className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/10",
42627
+ style: getSelectionStyle(selection, timeline)
42628
+ }
42629
+ ),
42630
+ draftSelection && /* @__PURE__ */ jsxRuntimeExports.jsx(
42631
+ "div",
42632
+ {
42633
+ className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/15",
42634
+ style: getDraftSelectionStyle(draftSelection)
42635
+ }
42636
+ ),
42637
+ timeline.rows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42638
+ TimelineTrack,
42639
+ {
42640
+ row,
42641
+ isSelected: selectedRequestId === row.request.id,
42642
+ onSelect: onRequestSelect,
42643
+ shouldSuppressSelect: () => suppressTrackClickRef.current
42644
+ },
42645
+ row.request.id
42646
+ )),
42647
+ 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: [
42648
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42649
+ filteredRequestCount,
42650
+ " in range"
42651
+ ] }),
42652
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42653
+ "button",
42654
+ {
42655
+ type: "button",
42656
+ title: "Clear timeline selection",
42657
+ "aria-label": "Clear timeline selection",
42658
+ className: "rounded p-0.5 text-gray-400 hover:bg-gray-800 hover:text-gray-100",
42659
+ onClick: () => onSelectionChange(null),
42660
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { className: "h-3 w-3" })
42661
+ }
42662
+ )
42663
+ ] }),
42664
+ 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: [
42665
+ "Showing latest ",
42666
+ timeline.rows.length,
42667
+ " of",
42668
+ " ",
42669
+ timeline.totalRequestCount
42670
+ ] })
42671
+ ]
42672
+ }
42673
+ ) });
42674
+ };
42675
+ const parseThreshold = (value) => {
42676
+ const normalizedValue = value.trim();
42677
+ if (!normalizedValue) {
42678
+ return null;
42679
+ }
42680
+ const parsedValue = Number(normalizedValue);
42681
+ return Number.isFinite(parsedValue) ? parsedValue : null;
42682
+ };
42683
+ const matchesStatusFilter = (statusCode, statusFilter) => {
42684
+ const normalizedFilter = statusFilter.trim().toLowerCase();
42685
+ if (!normalizedFilter) {
42686
+ return true;
42687
+ }
42688
+ if (statusCode === void 0) {
42689
+ return false;
42690
+ }
42691
+ const statusRangeMatch = normalizedFilter.match(/^(\d{3})\s*-\s*(\d{3})$/);
42692
+ if (statusRangeMatch) {
42693
+ const min2 = Number(statusRangeMatch[1]);
42694
+ const max2 = Number(statusRangeMatch[2]);
42695
+ return statusCode >= min2 && statusCode <= max2;
42696
+ }
42697
+ const statusClassMatch = normalizedFilter.match(/^([1-5])xx$/);
42698
+ if (statusClassMatch) {
42699
+ return Math.floor(statusCode / 100) === Number(statusClassMatch[1]);
42700
+ }
42701
+ const comparisonMatch = normalizedFilter.match(/^(>=|<=|>|<)\s*(\d{3})$/);
42702
+ if (comparisonMatch) {
42703
+ const value = Number(comparisonMatch[2]);
42704
+ switch (comparisonMatch[1]) {
42705
+ case ">=":
42706
+ return statusCode >= value;
42707
+ case "<=":
42708
+ return statusCode <= value;
42709
+ case ">":
42710
+ return statusCode > value;
42711
+ case "<":
42712
+ return statusCode < value;
42713
+ }
42714
+ }
42715
+ return statusCode === Number(normalizedFilter);
42716
+ };
42717
+ const isInFlightStatus = (status) => {
42718
+ return ["pending", "loading", "connecting", "open"].includes(status);
42719
+ };
42720
+ const isFailedStatus = (status) => {
42721
+ return ["failed", "error"].includes(status);
42722
+ };
42723
+ const isHttpMethod = (method) => method !== "WS" && method !== "SSE";
42724
+ const extractDomainAndPath = (url) => {
42725
+ try {
42726
+ const { hostname, pathname, search, hash: hash2, port } = new URL(url);
42727
+ return {
42728
+ domain: `${hostname}${port ? `:${port}` : ""}`,
42729
+ path: `${pathname}${search}${hash2}`
42730
+ };
42731
+ } catch {
42732
+ return { domain: "unknown", path: url };
42733
+ }
42734
+ };
42735
+ const matchesRequestFilter = (request, filter, options = {}) => {
42736
+ if (filter.types.size > 0 && !filter.types.has(request.type)) {
42737
+ return false;
42738
+ }
42739
+ if (filter.advanced.methods.size > 0 && (!isHttpMethod(request.method) || !filter.advanced.methods.has(request.method))) {
42740
+ return false;
42741
+ }
42742
+ if (filter.advanced.sources.size > 0 && (!request.source || !filter.advanced.sources.has(request.source))) {
42743
+ return false;
42744
+ }
42745
+ if (!matchesStatusFilter(request.httpStatus, filter.advanced.status)) {
42746
+ return false;
42747
+ }
42748
+ const { domain, path } = extractDomainAndPath(request.name);
42749
+ const domainFilter = filter.advanced.domain.trim().toLowerCase();
42750
+ if (domainFilter && !domain.toLowerCase().includes(domainFilter)) {
42751
+ return false;
42752
+ }
42753
+ const contentTypeFilter = filter.advanced.contentType.trim().toLowerCase();
42754
+ if (contentTypeFilter && !request.contentType?.toLowerCase().includes(contentTypeFilter)) {
42755
+ return false;
42756
+ }
42757
+ if (filter.advanced.failedOnly && !isFailedStatus(request.status)) {
42758
+ return false;
42759
+ }
42760
+ if (filter.advanced.inFlightOnly && !isInFlightStatus(request.status)) {
42761
+ return false;
42762
+ }
42763
+ if (filter.advanced.overriddenOnly && !options.hasOverride) {
42764
+ return false;
42765
+ }
42766
+ const minSize = parseThreshold(filter.advanced.minSize);
42767
+ if (minSize !== null && (request.size === null || request.size < minSize)) {
42768
+ return false;
42769
+ }
42770
+ const maxSize = parseThreshold(filter.advanced.maxSize);
42771
+ if (maxSize !== null && (request.size === null || request.size > maxSize)) {
42772
+ return false;
42773
+ }
42774
+ const duration = request.duration || 0;
42775
+ const minDuration = parseThreshold(filter.advanced.minDuration);
42776
+ if (minDuration !== null && duration < minDuration) {
42777
+ return false;
42778
+ }
42779
+ const maxDuration = parseThreshold(filter.advanced.maxDuration);
42780
+ if (maxDuration !== null && duration > maxDuration) {
42781
+ return false;
42782
+ }
42783
+ const searchText = filter.text.trim().toLowerCase();
42784
+ if (!searchText) {
42785
+ return true;
42786
+ }
42787
+ const searchableFields = [
42788
+ request.name,
42789
+ request.method,
42790
+ request.status,
42791
+ request.httpStatus,
42792
+ request.source,
42793
+ request.type,
42794
+ request.contentType,
42795
+ domain,
42796
+ path
42797
+ ].filter((value) => value !== void 0 && value !== null).join(" ").toLowerCase();
42798
+ return searchableFields.includes(searchText);
42799
+ };
42080
42800
  const InspectorView = ({ client: client2 }) => {
42081
42801
  const actions = useNetworkActivityActions();
42082
42802
  const clientManagement = useNetworkActivityClientManagement();
42083
42803
  const hasSelectedRequest = useHasSelectedRequest();
42084
42804
  const overrides = useOverrides();
42805
+ const processedRequests = useProcessedRequests();
42085
42806
  const [filter, setFilter] = reactExports.useState(
42086
42807
  () => createDefaultFilter()
42087
42808
  );
42809
+ const [timelineSelection, setTimelineSelection] = reactExports.useState(null);
42810
+ const filteredRequests = reactExports.useMemo(() => {
42811
+ return processedRequests.filter(
42812
+ (request) => matchesRequestFilter(request, filter, {
42813
+ hasOverride: overrides.has(request.name)
42814
+ })
42815
+ );
42816
+ }, [processedRequests, filter, overrides]);
42817
+ const visibleRequests = reactExports.useMemo(() => {
42818
+ if (!timelineSelection) {
42819
+ return filteredRequests;
42820
+ }
42821
+ const now = Date.now();
42822
+ return filteredRequests.filter(
42823
+ (request) => requestOverlapsTimelineRange(request, timelineSelection, now)
42824
+ );
42825
+ }, [filteredRequests, timelineSelection]);
42088
42826
  reactExports.useEffect(() => {
42089
42827
  if (!client2) {
42090
42828
  return;
@@ -42103,11 +42841,22 @@ const InspectorView = ({ client: client2 }) => {
42103
42841
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, {}),
42104
42842
  /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar, { filter, onFilterChange: setFilter }),
42105
42843
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
42106
- /* @__PURE__ */ jsxRuntimeExports.jsx(
42844
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
42107
42845
  "div",
42108
42846
  {
42109
42847
  className: `flex flex-col ${hasSelectedRequest ? "w-1/2" : "w-full"} border-r border-gray-700 overflow-hidden`,
42110
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(RequestList, { filter })
42848
+ children: [
42849
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42850
+ NetworkTimeline,
42851
+ {
42852
+ requests: filteredRequests,
42853
+ selection: timelineSelection,
42854
+ filteredRequestCount: visibleRequests.length,
42855
+ onSelectionChange: setTimelineSelection
42856
+ }
42857
+ ),
42858
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RequestList, { requests: visibleRequests })
42859
+ ]
42111
42860
  }
42112
42861
  ),
42113
42862
  hasSelectedRequest && /* @__PURE__ */ jsxRuntimeExports.jsx(SidePanel, {})