mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 21:25:45 +00:00
timeline ui updates wip
This commit is contained in:
@@ -139,7 +139,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="w-full h-full relative"
|
||||
className="w-full h-full relative flex"
|
||||
style={{ maxWidth: width }}
|
||||
queryFilterEnabled={queryFilterEnabled}
|
||||
>
|
||||
|
||||
@@ -2,13 +2,11 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.timeline-event {
|
||||
border-bottom: 1px solid ${(props) => props.theme.colors.text.muted};
|
||||
padding: 8px 0;
|
||||
padding: 8px 0 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.timeline-event-content {
|
||||
background: ${(props) => props.theme.requestTabs.bg};
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-top: 0.5rem;
|
||||
@@ -83,13 +81,10 @@ const StyledWrapper = styled.div`
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
color: ${(props) => props.theme.text};
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.oauth-request-item-content {
|
||||
background: ${(props) => props.theme.requestTabs.bg};
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -98,7 +93,6 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.section-header {
|
||||
cursor: pointer;
|
||||
padding: 8px 0;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import QueryResult from "components/ResponsePane/QueryResult/index";
|
||||
import { useState } from "react";
|
||||
|
||||
const BodyBlock = ({ collection, data, dataBuffer, headers, error, item, width }) => {
|
||||
const [isBodyCollapsed, toggleBody] = useState(true);
|
||||
return (
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => toggleBody(!isBodyCollapsed)}>
|
||||
<pre className="flex flex-row items-center text-lg text-indigo-500/80 dark:text-indigo-500/80">
|
||||
<div className="opacity-70">{isBodyCollapsed ? '▼' : '▶'}</div> Body
|
||||
</pre>
|
||||
</div>
|
||||
{isBodyCollapsed && (
|
||||
<div className="mt-2">
|
||||
{data || dataBuffer ? (
|
||||
<div className="h-96 overflow-auto">
|
||||
<QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
data={data}
|
||||
dataBuffer={dataBuffer}
|
||||
headers={headers}
|
||||
error={error}
|
||||
key={item?.uid}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500">No Body found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BodyBlock;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useState } from "react";
|
||||
|
||||
const HeadersBlock = ({ headers, type }) => {
|
||||
const [areHeadersCollapsed, toggleHeaders] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="collapsible-section mt-2">
|
||||
<div className="section-header" onClick={() => toggleHeaders(!areHeadersCollapsed)}>
|
||||
<pre className="flex flex-row items-center text-lg text-indigo-500/80 dark:text-indigo-500/80">
|
||||
<div className="opacity-70">{areHeadersCollapsed ? '▼' : '▶'}</div> Headers
|
||||
{headers && Object.keys(headers).length > 0 &&
|
||||
<div className="ml-1">({Object.keys(headers).length})</div>
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
{areHeadersCollapsed && (
|
||||
<div className="mt-1">
|
||||
{headers && Object.keys(headers).length > 0
|
||||
? <Headers headers={headers} type={type} />
|
||||
: <div className="text-gray-500">No Headers found</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
const Headers = ({ headers, type }) => {
|
||||
if (Array.isArray(headers)) {
|
||||
return (
|
||||
<div className="mt-1 text-sm">
|
||||
{headers.map((header, index) => (
|
||||
<pre key={index} className="mb-1 whitespace-pre-wrap">
|
||||
{type === 'request' ? '>' : '<'} <span className="opacity-60">{header?.name}:</span>
|
||||
<span className="whitespace-pre-wrap">{String(header?.value)}</span>
|
||||
</pre>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="mt-1 text-sm">
|
||||
{Object.entries(headers).map(([key, value], index) => (
|
||||
<pre key={index} className="mb-1 whitespace-pre-wrap">
|
||||
{type === 'request' ? '>' : '<'} <span className="opacity-60">{key}:</span>
|
||||
<span>{String(value)}</span>
|
||||
</pre>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default HeadersBlock;
|
||||
@@ -0,0 +1,19 @@
|
||||
const Method = ({ method }) => {
|
||||
return (
|
||||
<span className={`${methodColors[method?.toUpperCase()] || 'text-white'} font-bold`}>
|
||||
{method?.toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const methodColors = {
|
||||
GET: 'text-green-500',
|
||||
POST: 'text-blue-500',
|
||||
PUT: 'text-yellow-500',
|
||||
DELETE: 'text-red-500',
|
||||
PATCH: 'text-purple-500',
|
||||
OPTIONS: 'text-gray-500',
|
||||
HEAD: 'text-gray-500',
|
||||
};
|
||||
|
||||
export default Method;
|
||||
@@ -0,0 +1,26 @@
|
||||
const Status = ({ statusCode, statusText }) => {
|
||||
return (
|
||||
<span
|
||||
className={`${
|
||||
statusColor(statusCode) || 'text-white'
|
||||
} font-bold`}
|
||||
>
|
||||
{statusCode}{' '}
|
||||
{statusText || ''}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const statusColor = (statusCode) => {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'text-green-500';
|
||||
} else if (statusCode >= 300 && statusCode < 400) {
|
||||
return 'text-yellow-500';
|
||||
} else if (statusCode >= 400 && statusCode < 600) {
|
||||
return 'text-red-500';
|
||||
} else {
|
||||
return 'text-gray-500';
|
||||
}
|
||||
};
|
||||
|
||||
export default Status;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const getRelativeTime = (date) => {
|
||||
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
|
||||
const diff = (date - new Date()) / 1000;
|
||||
|
||||
const timeUnits = [
|
||||
{ unit: 'year', seconds: 31536000 },
|
||||
{ unit: 'month', seconds: 2592000 },
|
||||
{ unit: 'week', seconds: 604800 },
|
||||
{ unit: 'day', seconds: 86400 },
|
||||
{ unit: 'hour', seconds: 3600 },
|
||||
{ unit: 'minute', seconds: 60 },
|
||||
{ unit: 'second', seconds: 1 }
|
||||
];
|
||||
|
||||
for (const { unit, seconds } of timeUnits) {
|
||||
if (Math.abs(diff) >= seconds || unit === 'second') {
|
||||
return rtf.format(Math.round(diff / seconds), unit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RelativeTime = ({ timestamp }) => {
|
||||
const [relativeTime, setRelativeTime] = useState(getRelativeTime(new Date(timestamp)));
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setRelativeTime(getRelativeTime(new Date(timestamp)));
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [timestamp]);
|
||||
|
||||
return <pre className="text-xs">{relativeTime}</pre>;
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
const Network = ({ logs }) => {
|
||||
return (
|
||||
<div className="bg-black/5 text-white network-logs rounded overflow-auto h-96">
|
||||
<pre className="whitespace-pre-wrap">
|
||||
{logs.map((entry, index) => (
|
||||
<NetworkLogsEntry key={index} entry={entry} />
|
||||
))}
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const NetworkLogsEntry = ({ entry }) => {
|
||||
const { type, message } = entry;
|
||||
let className = '';
|
||||
|
||||
switch (type) {
|
||||
case 'request':
|
||||
className = 'text-blue-500';
|
||||
break;
|
||||
case 'response':
|
||||
className = 'text-green-500';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'text-red-500';
|
||||
break;
|
||||
case 'tls':
|
||||
className = 'text-purple-500';
|
||||
break;
|
||||
case 'info':
|
||||
className = 'text-yellow-500';
|
||||
break;
|
||||
default:
|
||||
className = 'text-gray-400';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default Network;
|
||||
@@ -0,0 +1,23 @@
|
||||
import Headers from "../Common/Headers/index";
|
||||
import BodyBlock from "../Common/Body/index";
|
||||
|
||||
const Request = ({ collection, request, item, width }) => {
|
||||
const { url, headers, data, dataBuffer, error } = request || {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Method and URL */}
|
||||
<div className="mb-1 flex gap-2">
|
||||
<pre className="whitespace-pre-wrap">{url}</pre>
|
||||
</div>
|
||||
|
||||
{/* Headers */}
|
||||
<Headers headers={headers} type={'request'} />
|
||||
|
||||
{/* Body */}
|
||||
<BodyBlock collection={collection} data={data} dataBuffer={dataBuffer} error={error} headers={headers} item={item} width={width} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Request;
|
||||
@@ -0,0 +1,26 @@
|
||||
import BodyBlock from "../Common/Body/index";
|
||||
import Headers from "../Common/Headers/index";
|
||||
import Status from "../Common/Status/index";
|
||||
|
||||
const Response = ({ collection, response, item, width }) => {
|
||||
const { status, statusCode, statusText, headers, data, dataBuffer, error } = response || {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Status */}
|
||||
<div className="mb-1">
|
||||
<Status statusCode={status || statusCode} statusText={statusText} />
|
||||
{response.duration && <span className="text-sm text-gray-400 ml-2">{response.duration}ms</span>}
|
||||
{response.size && <span className="text-sm text-gray-400 ml-2">{response.size}B</span>}
|
||||
</div>
|
||||
|
||||
{/* Headers */}
|
||||
<Headers headers={headers} type={'response'} />
|
||||
|
||||
{/* Body */}
|
||||
<BodyBlock collection={collection} data={data} dataBuffer={dataBuffer} error={error} headers={headers} item={item} width={width} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Response;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useState } from "react";
|
||||
import Network from "./Network/index";
|
||||
import Request from "./Request/index";
|
||||
import Response from "./Response/index";
|
||||
import Method from "./Common/Method/index";
|
||||
import Status from "./Common/Status/index";
|
||||
import { RelativeTime } from "./Common/Time/index";
|
||||
|
||||
const TimelineItem = ({ timestamp, request, response, item, collection, width, isOauth2 }) => {
|
||||
const [isCollapsed, _toggleCollapse] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('request');
|
||||
const toggleCollapse = () => _toggleCollapse(prev => !prev);
|
||||
const { method, status, statusCode, statusText, url = '' } = request || {};
|
||||
const showNetworkLogs = response.timeline && response.timeline.length > 0;
|
||||
|
||||
return (
|
||||
<div className={`border-b-2 ${isOauth2 ? 'border-indigo-700/50' : 'border-amber-700/50' } py-2`}>
|
||||
<div className="oauth-request-item-header cursor-pointer" onClick={toggleCollapse}>
|
||||
<div className="flex justify-between items-center min-w-0">
|
||||
<div className="flex items-center space-x-2 min-w-0">
|
||||
<Method method={method} />
|
||||
<Status statusCode={status || statusCode} statusText={statusText} />
|
||||
{isOauth2 ? <pre className="opacity-50">[oauth2.0]</pre> : null}
|
||||
<pre className="opacity-70">[{new Date(timestamp).toISOString()}]</pre>
|
||||
</div>
|
||||
<span className="text-sm text-gray-400 flex-shrink-0 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<RelativeTime timestamp={timestamp} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="truncate text-sm mt-1">{url}</div>
|
||||
</div>
|
||||
{isCollapsed && (<div className="text-sm overflow-hidden">
|
||||
{/* Tabs */}
|
||||
<div className="tabs-switcher flex mb-4">
|
||||
<button
|
||||
className={`mr-4 ${activeTab === 'request' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('request')}
|
||||
>
|
||||
Request
|
||||
</button>
|
||||
<button
|
||||
className={`mr-4 ${activeTab === 'response' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('response')}
|
||||
>
|
||||
Response
|
||||
</button>
|
||||
{showNetworkLogs && (
|
||||
<button
|
||||
className={`${activeTab === 'networkLogs' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('networkLogs')}
|
||||
>
|
||||
Network Logs
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="tab-content">
|
||||
{/* Request Tab */}
|
||||
{activeTab === 'request' && (
|
||||
<Request request={request} item={item} collection={collection} width={width} />
|
||||
)}
|
||||
|
||||
{/* Response Tab */}
|
||||
{activeTab === 'response' && (
|
||||
<Response response={response} item={item} collection={collection} width={width} />
|
||||
)}
|
||||
|
||||
{/* Network Logs Tab */}
|
||||
{activeTab === 'networkLogs' && showNetworkLogs && (
|
||||
<Network logs={response?.timeline} />
|
||||
)}
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineItem;
|
||||
@@ -1,31 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import QueryResult from '../QueryResult/index';
|
||||
import { findItemInCollection, findParentItemInCollection } from 'utils/collections/index';
|
||||
import { get } from 'lodash';
|
||||
const iconv = require('iconv-lite');
|
||||
|
||||
const methodColors = {
|
||||
GET: 'text-green-500',
|
||||
POST: 'text-blue-500',
|
||||
PUT: 'text-yellow-500',
|
||||
DELETE: 'text-red-500',
|
||||
PATCH: 'text-purple-500',
|
||||
OPTIONS: 'text-gray-500',
|
||||
HEAD: 'text-gray-500',
|
||||
};
|
||||
|
||||
const statusColor = (statusCode) => {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'text-green-500';
|
||||
} else if (statusCode >= 300 && statusCode < 400) {
|
||||
return 'text-yellow-500';
|
||||
} else if (statusCode >= 400 && statusCode < 600) {
|
||||
return 'text-red-500';
|
||||
} else {
|
||||
return 'text-gray-500';
|
||||
}
|
||||
};
|
||||
import TimelineItem from './TimelineItem/index';
|
||||
|
||||
const getEffectiveAuthSource = (collection, item) => {
|
||||
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
|
||||
@@ -82,121 +59,57 @@ const Timeline = ({ collection, item, width }) => {
|
||||
return false;
|
||||
}).sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
const [openSections, setOpenSections] = useState(() =>
|
||||
combinedTimeline.map((_, index) => index === 0)
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="pb-4 w-full"
|
||||
style={{ maxWidth: width - 40, overflowWrap: 'break-word' }}
|
||||
className="pb-4 w-full flex flex-grow flex-col"
|
||||
style={{ maxWidth: width - 60, overflowWrap: 'break-word' }}
|
||||
>
|
||||
{combinedTimeline.map((event, index) => {
|
||||
const isOpen = openSections[index];
|
||||
const toggleOpen = () => {
|
||||
setOpenSections((prevState) => {
|
||||
const newState = [...prevState];
|
||||
newState[index] = !newState[index];
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
if (event.type === 'request') {
|
||||
const { request, response, timestamp } = event.data;
|
||||
const { data, timestamp } = event;
|
||||
const { request, response } = data;
|
||||
return (
|
||||
<div key={index} className="timeline-event border-b border-gray-700 py-2">
|
||||
<div
|
||||
className="timeline-event-header cursor-pointer"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<div className="flex justify-between items-center min-w-0 gap-2">
|
||||
<div className="flex items-center space-x-2 min-w-0 flex-1">
|
||||
<div className="flex items-center flex-shrink-0">
|
||||
<span
|
||||
className={`${
|
||||
methodColors[request.method?.toUpperCase()] || 'text-white'
|
||||
} font-bold`}
|
||||
>
|
||||
{request.method?.toUpperCase()}
|
||||
</span>{' '}
|
||||
</div>
|
||||
<span
|
||||
className={`${
|
||||
statusColor(response.status || request.statusCode) || 'text-white'
|
||||
} font-bold`}
|
||||
>
|
||||
{response.status || request.statusCode}{' '}
|
||||
{response.statusText || ''}
|
||||
</span>
|
||||
<div className="flex items-center flex-shrink-0 space-x-2">
|
||||
{response.duration && (
|
||||
<span className="text-sm text-gray-400">
|
||||
{response.duration}ms
|
||||
</span>
|
||||
)}
|
||||
{response.size && (
|
||||
<span className="text-sm text-gray-400">
|
||||
{response.size}B
|
||||
</span>
|
||||
)}
|
||||
{response.state && (
|
||||
<span className="text-sm text-gray-400">
|
||||
{response.state}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-400 flex-shrink-0 overflow-hidden text-ellipsis whitespace-nowrap" style={{ minWidth: '120px', maxWidth: '200px' }}>
|
||||
{new Date(timestamp).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="truncate text-sm text-[#9b9b9b] mt-1">{request.url}</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="timeline-event-content ml-4 mt-2">
|
||||
<RenderRequestResponse
|
||||
request={request}
|
||||
response={response}
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div key={index} className="timeline-event mb-2">
|
||||
<TimelineItem
|
||||
timestamp={timestamp}
|
||||
request={request}
|
||||
response={response}
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (event.type === 'oauth2') {
|
||||
const { data } = event;
|
||||
const { debugInfo, fetchedAt } = data;
|
||||
const flattenedRequests = flattenRequests(debugInfo);
|
||||
const { data, timestamp } = event;
|
||||
const { debugInfo } = data;
|
||||
return (
|
||||
<div key={index} className="timeline-event border-b border-gray-700 py-2">
|
||||
<div
|
||||
className="timeline-event-header cursor-pointer flex items-center"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<div key={index} className="timeline-event">
|
||||
<div className="timeline-event-header cursor-pointer flex items-center">
|
||||
<div className="flex items-center">
|
||||
<span>{isOpen ? '▼' : '▶'}</span>
|
||||
<span className="ml-2 font-bold">OAuth2.0 Calls</span>
|
||||
<span className="font-bold">OAuth2.0 Calls</span>
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="ml-4 mt-2">
|
||||
{flattenedRequests && flattenedRequests.length > 0 ? (
|
||||
flattenedRequests.map((data, idx) => (
|
||||
<OAuthRequestItem
|
||||
key={idx}
|
||||
request={data}
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width - 50}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
{debugInfo && debugInfo.length > 0 ? (
|
||||
debugInfo.map((data, idx) => (
|
||||
<div className='ml-4'>
|
||||
<TimelineItem
|
||||
key={idx}
|
||||
timestamp={timestamp}
|
||||
request={data?.request}
|
||||
response={data?.response}
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width - 50}
|
||||
isOauth2={true}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div>No debug information available.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -207,409 +120,4 @@ const Timeline = ({ collection, item, width }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const flattenRequests = (requests, level = 0) => {
|
||||
let flatList = [];
|
||||
requests.forEach((request) => {
|
||||
flatList.push({ ...request, isSubRequest: level > 0 });
|
||||
if (request.requests && request.requests.length > 0) {
|
||||
flatList = flatList.concat(flattenRequests(request.requests, level + 1));
|
||||
}
|
||||
});
|
||||
return flatList;
|
||||
};
|
||||
|
||||
// Helper function to process dataBuffer
|
||||
const processDataBuffer = (dataBuffer) => {
|
||||
if (dataBuffer) {
|
||||
try {
|
||||
let buffer;
|
||||
if (Buffer.isBuffer(dataBuffer)) {
|
||||
buffer = dataBuffer;
|
||||
} else if (typeof dataBuffer === 'string') {
|
||||
buffer = Buffer.from(dataBuffer, 'base64');
|
||||
} else if (dataBuffer instanceof Uint8Array || Array.isArray(dataBuffer)) {
|
||||
buffer = Buffer.from(dataBuffer);
|
||||
} else {
|
||||
return JSON.stringify(dataBuffer);
|
||||
}
|
||||
const dataRaw = iconv.decode(buffer, 'utf-8');
|
||||
const formattedData = dataRaw.replace(/^\uFEFF/, '');
|
||||
return formattedData;
|
||||
} catch (error) {
|
||||
console.error('Error processing dataBuffer:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const RenderRequestResponse = ({ request, response, item, collection, width }) => {
|
||||
const [activeTab, setActiveTab] = useState('request');
|
||||
const [isRequestHeadersOpen, setIsRequestHeadersOpen] = useState(false);
|
||||
const [isResponseHeadersOpen, setIsResponseHeadersOpen] = useState(false);
|
||||
const [isRequestCookiesOpen, setIsRequestCookiesOpen] = useState(false);
|
||||
const [isResponseCookiesOpen, setIsResponseCookiesOpen] = useState(false);
|
||||
const [isRequestBodyOpen, setIsRequestBodyOpen] = useState(false);
|
||||
const [isResponseBodyOpen, setIsResponseBodyOpen] = useState(false);
|
||||
|
||||
const requestHeaders = request.headers || request.requestHeaders || {};
|
||||
const responseHeaders = response.headers || response.responseHeaders || {};
|
||||
|
||||
const {
|
||||
cookies: requestCookies,
|
||||
headers: filteredRequestHeaders,
|
||||
} = separateCookiesAndHeaders(requestHeaders);
|
||||
const {
|
||||
cookies: responseCookies,
|
||||
headers: filteredResponseHeaders,
|
||||
} = separateCookiesAndHeaders(responseHeaders);
|
||||
|
||||
const showNetworkLogs = response.timeline && response.timeline.length > 0;
|
||||
|
||||
return (
|
||||
<div className="text-sm overflow-hidden">
|
||||
{/* Tabs */}
|
||||
<div className="tabs-switcher flex mb-4">
|
||||
<button
|
||||
className={`mr-4 ${activeTab === 'request' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('request')}
|
||||
>
|
||||
Request
|
||||
</button>
|
||||
<button
|
||||
className={`mr-4 ${activeTab === 'response' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('response')}
|
||||
>
|
||||
Response
|
||||
</button>
|
||||
{showNetworkLogs && (
|
||||
<button
|
||||
className={`${activeTab === 'networkLogs' ? 'active' : 'text-gray-400'}`}
|
||||
onClick={() => setActiveTab('networkLogs')}
|
||||
>
|
||||
Network Logs
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="tab-content">
|
||||
{/* Request Tab */}
|
||||
{activeTab === 'request' && (
|
||||
<div>
|
||||
{/* Method and URL */}
|
||||
<div className="mb-4">
|
||||
<span className={`${methodColors[request.method.toUpperCase()] || 'text-white'} font-bold`}>
|
||||
{request.method.toUpperCase()}
|
||||
</span>{' '}
|
||||
<span>{request.url}</span>
|
||||
</div>
|
||||
|
||||
{/* Headers */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsRequestHeadersOpen(!isRequestHeadersOpen)}>
|
||||
<span className="font-bold">
|
||||
{isRequestHeadersOpen ? '▼' : '▶'} Headers
|
||||
{filteredRequestHeaders && Object.keys(filteredRequestHeaders).length > 0 &&
|
||||
<sup className="ml-1 font-medium">({Object.keys(filteredRequestHeaders).length})</sup>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{isRequestHeadersOpen && (
|
||||
<div className="mt-2">
|
||||
{filteredRequestHeaders && Object.keys(filteredRequestHeaders).length > 0
|
||||
? renderHeaders(filteredRequestHeaders)
|
||||
: <div className="text-gray-500">No Headers found</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Cookies */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsRequestCookiesOpen(!isRequestCookiesOpen)}>
|
||||
<span className="font-bold">
|
||||
{isRequestCookiesOpen ? '▼' : '▶'} Cookies
|
||||
{requestCookies && Object.keys(requestCookies).length > 0 &&
|
||||
<sup className="ml-1 font-medium">({Object.keys(requestCookies).length})</sup>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{isRequestCookiesOpen && (
|
||||
<div className="mt-2">
|
||||
{requestCookies && Object.keys(requestCookies).length > 0
|
||||
? renderHeaders(requestCookies)
|
||||
: <div className="text-gray-500">No Cookies found</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsRequestBodyOpen(!isRequestBodyOpen)}>
|
||||
<span className="font-bold">
|
||||
{isRequestBodyOpen ? '▼' : '▶'} Body
|
||||
</span>
|
||||
</div>
|
||||
{isRequestBodyOpen && (
|
||||
<div className="mt-2">
|
||||
{request.data || request.dataBuffer || request?.requestBody ? (
|
||||
<div className="h-96 overflow-auto">
|
||||
<QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
data={request?.requestBody || request.data}
|
||||
dataBuffer={request.dataBuffer}
|
||||
headers={request.headers}
|
||||
error={request.error}
|
||||
key={item.filename}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500">No Body found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Response Tab */}
|
||||
{activeTab === 'response' && (
|
||||
<div>
|
||||
{/* Status */}
|
||||
<div className="mb-4">
|
||||
<span className={`${statusColor(response.status || request.statusCode) || 'text-white'} font-bold`}>
|
||||
{response.status || request.statusCode}
|
||||
</span>{' '}
|
||||
<span>{response.statusText || request.statusText || ''}</span>
|
||||
{response.duration && <span className="text-sm text-gray-400 ml-2">{response.duration}ms</span>}
|
||||
{response.size && <span className="text-sm text-gray-400 ml-2">{response.size}B</span>}
|
||||
</div>
|
||||
|
||||
{/* Headers */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsResponseHeadersOpen(!isResponseHeadersOpen)}>
|
||||
<span className="font-bold">
|
||||
{isResponseHeadersOpen ? '▼' : '▶'} Headers
|
||||
{filteredResponseHeaders && Object.keys(filteredResponseHeaders).length > 0 &&
|
||||
<sup className="ml-1 font-medium">({Object.keys(filteredResponseHeaders).length})</sup>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{isResponseHeadersOpen && (
|
||||
<div className="mt-2">
|
||||
{filteredResponseHeaders && Object.keys(filteredResponseHeaders).length > 0
|
||||
? renderHeaders(filteredResponseHeaders)
|
||||
: <div className="text-gray-500">No Headers found</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Cookies */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsResponseCookiesOpen(!isResponseCookiesOpen)}>
|
||||
<span className="font-bold">
|
||||
{isResponseCookiesOpen ? '▼' : '▶'} Cookies
|
||||
{responseCookies && Object.keys(responseCookies).length > 0 &&
|
||||
<sup className="ml-1 font-medium">({Object.keys(responseCookies).length})</sup>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{isResponseCookiesOpen && (
|
||||
<div className="mt-2">
|
||||
{responseCookies && Object.keys(responseCookies).length > 0
|
||||
? renderHeaders(responseCookies)
|
||||
: <div className="text-gray-500">No Cookies found</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="collapsible-section">
|
||||
<div className="section-header" onClick={() => setIsResponseBodyOpen(!isResponseBodyOpen)}>
|
||||
<span className="font-bold">
|
||||
{isResponseBodyOpen ? '▼' : '▶'} Body
|
||||
</span>
|
||||
</div>
|
||||
{isResponseBodyOpen && (
|
||||
<div className="mt-2">
|
||||
{response.data || response.dataBuffer ? (
|
||||
<div className="h-96 overflow-auto">
|
||||
<QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
data={response.data}
|
||||
dataBuffer={response.dataBuffer}
|
||||
headers={response.headers}
|
||||
error={response.error}
|
||||
key={item.filename}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500">No Body found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Network Logs Tab */}
|
||||
{activeTab === 'networkLogs' && showNetworkLogs && (
|
||||
<div className="bg-black/5 text-white p-2 network-logs rounded overflow-auto h-96">
|
||||
<pre className="whitespace-pre-wrap">
|
||||
{response.timeline.map((entry, index) => (
|
||||
<NetworkLogsEntry key={index} entry={entry} />
|
||||
))}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NetworkLogsEntry = ({ entry }) => {
|
||||
const { type, message } = entry;
|
||||
let className = '';
|
||||
|
||||
switch (type) {
|
||||
case 'request':
|
||||
className = 'text-blue-500';
|
||||
break;
|
||||
case 'response':
|
||||
className = 'text-green-500';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'text-red-500';
|
||||
break;
|
||||
case 'tls':
|
||||
className = 'text-purple-500';
|
||||
break;
|
||||
case 'info':
|
||||
className = 'text-yellow-500';
|
||||
break;
|
||||
default:
|
||||
className = 'text-gray-400';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
const renderHeaders = (data) => {
|
||||
if (Array.isArray(data)) {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
{data.map((header, index) => (
|
||||
<div key={index} className="flex mb-2">
|
||||
<div className="w-1/3 font-bold">{header.name}:</div>
|
||||
<div className="w-2/3">{String(header.value)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
{Object.entries(data).map(([key, value], index) => (
|
||||
<div key={index} className="flex mb-2">
|
||||
<div className="w-1/3 font-bold">{key}:</div>
|
||||
<div className="w-2/3">{String(value)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const separateCookiesAndHeaders = (headers) => {
|
||||
const cookies = {};
|
||||
let filteredHeaders = {};
|
||||
|
||||
if (Array.isArray(headers)) {
|
||||
filteredHeaders = headers.filter((header) => header.enabled !== false);
|
||||
filteredHeaders.forEach((header) => {
|
||||
if (
|
||||
header.name.toLowerCase() === 'cookie' ||
|
||||
header.name.toLowerCase() === 'set-cookie'
|
||||
) {
|
||||
cookies[header.name] = header.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (key.toLowerCase() === 'cookie' || key.toLowerCase() === 'set-cookie') {
|
||||
cookies[key] = value;
|
||||
} else {
|
||||
filteredHeaders[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { cookies, headers: filteredHeaders };
|
||||
};
|
||||
|
||||
const OAuthRequestItem = ({ request, item, collection, width }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const toggleOpen = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
const { isSubRequest } = request;
|
||||
const url = request.url || '';
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
||||
const isImage = imageExtensions.some((ext) => url.toLowerCase().endsWith(ext));
|
||||
|
||||
return (
|
||||
<div className="border-b border-gray-700 py-2">
|
||||
<div className="oauth-request-item-header cursor-pointer" onClick={toggleOpen}>
|
||||
<div className="flex justify-between items-center min-w-0">
|
||||
<div className="flex items-center space-x-2 min-w-0">
|
||||
<div className="flex items-center flex-shrink-0">
|
||||
<span className={`${methodColors[request.method?.toUpperCase()] || 'text-white'} font-bold`}>
|
||||
{request.method?.toUpperCase()}
|
||||
</span>{' '}
|
||||
</div>
|
||||
<span className={`${statusColor(request.statusCode) || 'text-white'} font-bold`}>
|
||||
{request.statusCode}
|
||||
{request.statusText || ''}
|
||||
</span>
|
||||
<div className="flex items-center flex-shrink-0 space-x-2">
|
||||
{isSubRequest && <span className="request-label">API Request</span>}
|
||||
{isImage && <span className="request-label">Image</span>}
|
||||
{request.duration && <span className="text-sm text-gray-400">{request.duration}ms</span>}
|
||||
{request.size && <span className="text-sm text-gray-400">{request.size}B</span>}
|
||||
{request.state && <span className="text-sm text-gray-400">{request.state}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="truncate text-sm text-[#9b9b9b] mt-1">{request.url}</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="oauth-request-item-content mt-2 text-sm">
|
||||
<RenderRequestResponse
|
||||
request={request}
|
||||
response={request}
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timeline;
|
||||
@@ -298,6 +298,8 @@ export const collectionsSlice = createSlice({
|
||||
? item.requestSent.timestamp.getTime()
|
||||
: item?.requestSent?.timestamp || Date.now();
|
||||
|
||||
console.log("response reieved", JSON.stringify(item), JSON.stringify(item.requestSent));
|
||||
|
||||
// Append the new timeline entry with numeric timestamp
|
||||
collection.timeline.push({
|
||||
type: "request",
|
||||
|
||||
@@ -187,6 +187,6 @@ export const stringifyIfNot = v => typeof v === 'string' ? v : String(v);
|
||||
|
||||
export const getEncoding = (headers) => {
|
||||
// Parse the charset from content type: https://stackoverflow.com/a/33192813
|
||||
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(headers['content-type'] || '');
|
||||
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(headers?.['content-type'] || '');
|
||||
return charsetMatch?.[1];
|
||||
}
|
||||
|
||||
@@ -38,114 +38,78 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
||||
|
||||
const { session: webSession } = window.webContents;
|
||||
|
||||
// Map to store request data using requestId as the key
|
||||
const requestMap = {};
|
||||
|
||||
// Intercept request events and gather data
|
||||
webSession.webRequest.onBeforeRequest((details, callback) => {
|
||||
const { id: requestId, url, method, resourceType, frameId } = details;
|
||||
|
||||
const request = {
|
||||
requestId,
|
||||
url,
|
||||
method,
|
||||
resourceType,
|
||||
frameId,
|
||||
timestamp: Date.now(),
|
||||
requestHeaders: {},
|
||||
responseHeaders: {},
|
||||
statusCode: null,
|
||||
error: null,
|
||||
fromCache: null,
|
||||
completed: false,
|
||||
};
|
||||
|
||||
requestMap[requestId] = request;
|
||||
|
||||
if (resourceType === 'mainFrame') {
|
||||
// This is a main frame request
|
||||
currentMainRequest = {
|
||||
requestId,
|
||||
url,
|
||||
method,
|
||||
timestamp: request.timestamp,
|
||||
requestHeaders: {},
|
||||
responseHeaders: {},
|
||||
statusCode: null,
|
||||
error: null,
|
||||
fromCache: null,
|
||||
completed: false,
|
||||
requests: [], // To hold sub-resource requests
|
||||
resourceType,
|
||||
frameId,
|
||||
request: {
|
||||
url,
|
||||
method,
|
||||
headers: {},
|
||||
error: null
|
||||
},
|
||||
response: {
|
||||
headers: {},
|
||||
status: null,
|
||||
statusText: null,
|
||||
error: null
|
||||
},
|
||||
fromCache: false,
|
||||
completed: true,
|
||||
requests: [], // No sub-requests in this context
|
||||
};
|
||||
// Add to mainRequests
|
||||
|
||||
// pushing the currentMainRequest to debugInfo
|
||||
// the currentMainRequest will be further updated by object reference
|
||||
debugInfo.data.push(currentMainRequest);
|
||||
} else if (currentMainRequest) {
|
||||
// Associate sub-resource request with current main request
|
||||
currentMainRequest.requests.push(request);
|
||||
}
|
||||
|
||||
callback({ cancel: false });
|
||||
});
|
||||
|
||||
webSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
const { id: requestId, requestHeaders } = details;
|
||||
if (requestMap[requestId]) {
|
||||
requestMap[requestId].requestHeaders = requestHeaders;
|
||||
const { id: requestId, requestHeaders, method, url } = details;
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.request = {
|
||||
url,
|
||||
headers: requestHeaders,
|
||||
method
|
||||
};
|
||||
}
|
||||
|
||||
if (requestMap[requestId]?.resourceType === 'mainFrame') {
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.requestHeaders = requestHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
callback({ cancel: false, requestHeaders });
|
||||
});
|
||||
|
||||
webSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
const { id: requestId, statusCode, responseHeaders } = details;
|
||||
if (requestMap[requestId]) {
|
||||
requestMap[requestId].statusCode = statusCode;
|
||||
requestMap[requestId].responseHeaders = responseHeaders;
|
||||
const { id: requestId, url, statusCode, responseHeaders, method } = details;
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.response = {
|
||||
url,
|
||||
method,
|
||||
status: statusCode,
|
||||
headers: responseHeaders
|
||||
};
|
||||
}
|
||||
|
||||
if (requestMap[requestId]?.resourceType === 'mainFrame') {
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.statusCode = statusCode;
|
||||
currentMainRequest.responseHeaders = responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
callback({ cancel: false, responseHeaders });
|
||||
});
|
||||
|
||||
webSession.webRequest.onCompleted((details) => {
|
||||
const { id: requestId, fromCache } = details;
|
||||
if (requestMap[requestId]) {
|
||||
requestMap[requestId].completed = true;
|
||||
requestMap[requestId].fromCache = fromCache;
|
||||
}
|
||||
|
||||
// If this is a mainFrame request, update currentMainRequest
|
||||
if (requestMap[requestId]?.resourceType === 'mainFrame') {
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.completed = true;
|
||||
currentMainRequest.fromCache = fromCache;
|
||||
}
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.completed = true;
|
||||
currentMainRequest.fromCache = fromCache;
|
||||
}
|
||||
});
|
||||
|
||||
webSession.webRequest.onErrorOccurred((details) => {
|
||||
const { id: requestId, error } = details;
|
||||
if (requestMap[requestId]) {
|
||||
requestMap[requestId].error = error;
|
||||
}
|
||||
|
||||
// If this is a mainFrame request, update currentMainRequest
|
||||
if (requestMap[requestId]?.resourceType === 'mainFrame') {
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.error = error;
|
||||
}
|
||||
if (currentMainRequest?.requestId === requestId) {
|
||||
currentMainRequest.response.error = error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -204,7 +168,6 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
||||
try {
|
||||
const callbackUrlWithCode = new URL(finalUrl);
|
||||
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
||||
|
||||
return resolve({ authorizationCode, debugInfo });
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
|
||||
@@ -132,6 +132,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
}
|
||||
requestCopy.data = data;
|
||||
requestCopy.url = url;
|
||||
requestCopy.responseType = 'arraybuffer';
|
||||
|
||||
// Initialize variables to hold request and response data for debugging
|
||||
let axiosRequestInfo = null;
|
||||
@@ -154,6 +155,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
// Interceptor to capture response data
|
||||
axiosInstance.interceptors.response.use((response) => {
|
||||
axiosResponseInfo = {
|
||||
url: response?.url,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
@@ -164,6 +166,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
}, (error) => {
|
||||
if (error.response) {
|
||||
axiosResponseInfo = {
|
||||
url: error?.response?.url,
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
headers: error.response.headers,
|
||||
@@ -187,17 +190,23 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
|
||||
// Add the axios request and response info as a main request in debugInfo
|
||||
const axiosMainRequest = {
|
||||
requestId: Date.now().toString(), // Generate a unique requestId
|
||||
url: axiosRequestInfo.url,
|
||||
method: axiosRequestInfo.method,
|
||||
timestamp: axiosRequestInfo.timestamp,
|
||||
requestHeaders: axiosRequestInfo.headers || {},
|
||||
requestBody: axiosRequestInfo.data,
|
||||
responseHeaders: axiosResponseInfo.headers || {},
|
||||
data: parsedResponseData,
|
||||
statusCode: axiosResponseInfo.status || null,
|
||||
statusMessage: axiosResponseInfo.statusText || null,
|
||||
error: null,
|
||||
requestId: Date.now().toString(),
|
||||
request: {
|
||||
url: axiosRequestInfo?.url,
|
||||
method: axiosRequestInfo?.method,
|
||||
headers: axiosRequestInfo?.headers || {},
|
||||
body: axiosRequestInfo?.data,
|
||||
error: null
|
||||
},
|
||||
response: {
|
||||
url: axiosResponseInfo?.url,
|
||||
headers: axiosResponseInfo?.headers,
|
||||
data: parsedResponseData,
|
||||
dataBuffer: axiosResponseInfo?.data,
|
||||
status: axiosResponseInfo?.status,
|
||||
statusText: axiosResponseInfo?.statusText,
|
||||
error: null
|
||||
},
|
||||
fromCache: false,
|
||||
completed: true,
|
||||
requests: [], // No sub-requests in this context
|
||||
@@ -335,6 +344,7 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
}
|
||||
requestCopy.data = data;
|
||||
requestCopy.url = url;
|
||||
requestCopy.responseType = 'arraybuffer';
|
||||
|
||||
// Initialize variables to hold request and response data for debugging
|
||||
let axiosRequestInfo = null;
|
||||
@@ -358,6 +368,7 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
// Interceptor to capture response data
|
||||
axiosInstance.interceptors.response.use((response) => {
|
||||
axiosResponseInfo = {
|
||||
url: response.url,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
@@ -368,6 +379,7 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
}, (error) => {
|
||||
if (error.response) {
|
||||
axiosResponseInfo = {
|
||||
url: error?.response?.url,
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
headers: error.response.headers,
|
||||
@@ -385,16 +397,22 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
// Add the axios request and response info as a main request in debugInfo
|
||||
const axiosMainRequest = {
|
||||
requestId: Date.now().toString(),
|
||||
url: axiosRequestInfo.url,
|
||||
method: axiosRequestInfo.method,
|
||||
timestamp: axiosRequestInfo.timestamp,
|
||||
requestHeaders: axiosRequestInfo.headers || {},
|
||||
requestBody: axiosRequestInfo.data,
|
||||
responseHeaders: axiosResponseInfo.headers || {},
|
||||
responseBody: parsedResponseData,
|
||||
statusCode: axiosResponseInfo.status || null,
|
||||
statusMessage: axiosResponseInfo.statusText || null,
|
||||
error: null,
|
||||
request: {
|
||||
url: axiosRequestInfo?.url,
|
||||
method: axiosRequestInfo?.method,
|
||||
headers: axiosRequestInfo?.headers || {},
|
||||
body: axiosRequestInfo?.data,
|
||||
error: null
|
||||
},
|
||||
response: {
|
||||
url: axiosResponseInfo.url,
|
||||
headers: axiosResponseInfo?.headers,
|
||||
data: parsedResponseData,
|
||||
dataBuffer: axiosResponseInfo?.data?.toString('base64'),
|
||||
status: axiosResponseInfo?.status,
|
||||
statusText: axiosResponseInfo?.statusText,
|
||||
error: null
|
||||
},
|
||||
fromCache: false,
|
||||
completed: true,
|
||||
requests: [], // No sub-requests in this context
|
||||
@@ -500,6 +518,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
}
|
||||
requestCopy.data = data;
|
||||
requestCopy.url = url;
|
||||
requestCopy.responseType = 'arraybuffer';
|
||||
|
||||
// Initialize variables to hold request and response data for debugging
|
||||
let axiosRequestInfo = null;
|
||||
@@ -523,6 +542,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
// Interceptor to capture response data
|
||||
axiosInstance.interceptors.response.use((response) => {
|
||||
axiosResponseInfo = {
|
||||
url: response.url,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
@@ -533,6 +553,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
}, (error) => {
|
||||
if (error.response) {
|
||||
axiosResponseInfo = {
|
||||
url: error?.response?.url,
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
headers: error.response.headers,
|
||||
@@ -550,16 +571,22 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
// Add the axios request and response info as a main request in debugInfo
|
||||
const axiosMainRequest = {
|
||||
requestId: Date.now().toString(),
|
||||
url: axiosRequestInfo.url,
|
||||
method: axiosRequestInfo.method,
|
||||
timestamp: axiosRequestInfo.timestamp,
|
||||
requestHeaders: axiosRequestInfo.headers || {},
|
||||
requestBody: axiosRequestInfo.data,
|
||||
responseHeaders: axiosResponseInfo.headers || {},
|
||||
responseBody: parsedResponseData,
|
||||
statusCode: axiosResponseInfo.status || null,
|
||||
statusMessage: axiosResponseInfo.statusText || null,
|
||||
error: null,
|
||||
request: {
|
||||
url: axiosRequestInfo?.url,
|
||||
method: axiosRequestInfo?.method,
|
||||
headers: axiosRequestInfo?.headers || {},
|
||||
body: axiosRequestInfo?.data,
|
||||
error: null
|
||||
},
|
||||
response: {
|
||||
url: axiosResponseInfo?.url,
|
||||
headers: axiosResponseInfo?.headers,
|
||||
data: parsedResponseData,
|
||||
dataBuffer: axiosResponseInfo?.data?.toString('base64'),
|
||||
status: axiosResponseInfo?.status,
|
||||
statusText: axiosResponseInfo?.statusText,
|
||||
error: null
|
||||
},
|
||||
fromCache: false,
|
||||
completed: true,
|
||||
requests: [], // No sub-requests in this context
|
||||
|
||||
@@ -6,7 +6,7 @@ meta {
|
||||
|
||||
get {
|
||||
url: {{key-host}}/realms/bruno/protocol/openid-connect/userinfo
|
||||
body: none
|
||||
body: json
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ auth:oauth2 {
|
||||
callback_url: {{key-host}}/realms/bruno/account
|
||||
authorization_url: {{key-host}}/realms/bruno/protocol/openid-connect/auth
|
||||
access_token_url: {{key-host}}/realms/bruno/protocol/openid-connect/token
|
||||
refresh_url:
|
||||
client_id: account
|
||||
client_secret: Lh3NkRikMZpO12rwSBwVimde9v89B5Rw
|
||||
scope: openid
|
||||
@@ -24,5 +25,6 @@ auth:oauth2 {
|
||||
credentials_id: credentials
|
||||
token_placement: header
|
||||
token_header_prefix: Bearer
|
||||
reuse_token:
|
||||
auto_fetch_token: true
|
||||
auto_refresh_token: true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user