Merge pull request #4309 from lohxt1/timeline_fixes

oauth2 related fixes
This commit is contained in:
lohit
2025-03-25 12:22:30 +05:30
committed by GitHub
14 changed files with 177 additions and 47 deletions

View File

@@ -46,11 +46,11 @@ const Auth = ({ collection, folder }) => {
case 'oauth2': {
return (
<>
<GrantTypeSelector
<GrantTypeSelector
request={request}
updateAuth={updateFolderAuth}
collection={collection}
folder={folder}
item={folder}
/>
<GrantTypeComponentMap collection={collection} folder={folder} />
</>

View File

@@ -152,7 +152,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
{(script.req || script.res) && (
item.preScriptResponseErrorMessage || item.postResponseScriptErrorMessage ?
item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ?
<ErrorIndicator /> :
<ContentIndicator />
)}

View File

@@ -0,0 +1,8 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
font-size: 0.8125rem;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
`;
export default StyledWrapper;

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { clearRequestTimeline } from 'providers/ReduxStore/slices/collections/index';
const ClearTimeline = ({ collection, item }) => {
const dispatch = useDispatch();
const clearResponse = () =>
dispatch(
clearRequestTimeline({
itemUid: item.uid,
collectionUid: collection.uid
})
);
return (
<StyledWrapper className="ml-2 flex items-center">
<button onClick={clearResponse} className='text-link hover:underline' title="Clear Timeline">
Clear Timeline
</button>
</StyledWrapper>
);
};
export default ClearTimeline;

View File

@@ -27,7 +27,7 @@ export const RelativeTime = ({ timestamp }) => {
useEffect(() => {
const interval = setInterval(() => {
setRelativeTime(getRelativeTime(new Date(timestamp)));
}, 1000);
}, 60000);
return () => clearInterval(interval);
}, [timestamp]);

View File

@@ -18,6 +18,7 @@ import ScriptErrorIcon from './ScriptErrorIcon';
import StyledWrapper from './StyledWrapper';
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
import ResponseClear from 'src/components/ResponsePane/ResponseClear';
import ClearTimeline from './ClearTimeline/index';
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const dispatch = useDispatch();
@@ -26,6 +27,10 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const isLoading = ['queued', 'sending'].includes(item.requestState);
const [showScriptErrorCard, setShowScriptErrorCard] = useState(false);
const requestTimeline = ([...(collection.timeline || [])]).filter(obj => {
if (obj.itemUid === item.uid) return true;
});
useEffect(() => {
if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage) {
setShowScriptErrorCard(true);
@@ -83,7 +88,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
);
}
if (!item.response) {
if (!item.response && !requestTimeline?.length) {
return (
<StyledWrapper className="flex h-full relative">
<Placeholder />
@@ -134,11 +139,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
onClick={() => setShowScriptErrorCard(true)}
/>
)}
<ResponseClear item={item} collection={collection} />
<ResponseSave item={item} />
<StatusCode status={response.status} />
<ResponseTime duration={response.duration} />
<ResponseSize size={response.size} />
{focusedTab?.responsePaneTab === "timeline" ? (
<ClearTimeline item={item} collection={collection} />
) : item?.response ? (
<>
<ResponseClear item={item} collection={collection} />
<ResponseSave item={item} />
<StatusCode status={response.status} />
<ResponseTime duration={response.duration} />
<ResponseSize size={response.size} />
</>
) : null}
</div>
) : null}
</div>
@@ -152,7 +163,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
onClose={() => setShowScriptErrorCard(false)}
/>
)}
{getTabPanel(focusedTab.responsePaneTab)}
{!item?.response ? (
focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
<Timeline
collection={collection}
item={item}
width={rightPaneWidth}
/>
) : null
) : (
<>{getTabPanel(focusedTab.responsePaneTab)}</>
)}
</section>
</StyledWrapper>
);

View File

@@ -1297,12 +1297,13 @@ export const refreshOauth2Credentials = (payload) => async (dispatch, getState)
return new Promise((resolve, reject) => {
window.ipcRenderer
.invoke('renderer:refresh-oauth2-credentials', { request, collection })
.then(({ credentials, url, collectionUid, debugInfo }) => {
.then(({ credentials, url, collectionUid, debugInfo, credentialsId }) => {
dispatch(
collectionAddOauth2CredentialsByUrl({
credentials,
url,
collectionUid,
credentialsId,
debugInfo,
folderUid: folderUid || null,
itemId: !folderUid ? itemId : null
@@ -1320,8 +1321,12 @@ export const clearOauth2Cache = (payload) => async (dispatch, getState) => {
window.ipcRenderer
.invoke('clear-oauth2-cache', collectionUid, url, credentialsId)
.then(() => {
// We do not dispatch any action to modify the Redux store,
// since we are only clearing the session on the main process side.
dispatch(
collectionClearOauth2CredentialsByUrl({
url,
collectionUid,
})
);
resolve();
})
.catch(reject);

View File

@@ -324,6 +324,23 @@ export const collectionsSlice = createSlice({
}
}
},
clearTimeline: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
collection.timeline = [];
}
},
clearRequestTimeline: (state, action) => {
const { collectionUid, itemUid } = action.payload || {};
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
if (itemUid) {
collection.timeline = collection?.timeline?.filter(t => t?.itemUid !== itemUid);
}
}
},
saveRequest: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -2119,7 +2136,19 @@ export const collectionsSlice = createSlice({
},
collectionClearOauth2CredentialsByUrl: (state, action) => {
// Since we don't want to remove tokens from oauth2Credentials or timeline,
const { collectionUid, url, credentialsId } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (!collection) return;
if (collection.oauth2Credentials) {
let collectionOauth2Credentials = cloneDeep(collection.oauth2Credentials);
const filteredOauth2Credentials = filter(
collectionOauth2Credentials,
(creds) =>
!(creds.url === url && creds.collectionUid === collectionUid)
);
collection.oauth2Credentials = filteredOauth2Credentials;
}
},
collectionGetOauth2CredentialsByUrl: (state, action) => {
@@ -2167,6 +2196,8 @@ export const {
requestCancelled,
responseReceived,
responseCleared,
clearTimeline,
clearRequestTimeline,
saveRequest,
deleteRequestDraft,
newEphemeralHttpRequest,

View File

@@ -25,8 +25,11 @@ const getStoredOauth2Credentials = ({ collectionUid, url, credentialsId }) => {
};
const isTokenExpired = (credentials) => {
if (!credentials || !credentials.expires_in || !credentials.created_at) {
return true; // Assume expired if missing data
if (!credentials?.access_token) {
return true;
}
if (!credentials?.expires_in || !credentials.created_at) {
return false;
}
const expiryTime = credentials.created_at + credentials.expires_in * 1000;
return Date.now() > expiryTime;

View File

@@ -1,6 +1,6 @@
const ohm = require('ohm-js');
const _ = require('lodash');
const { outdentString } = require('../../v1/src/utils');
const { safeParseJson, outdentString } = require('./utils');
/**
* A Bru file is made up of blocks.
@@ -545,8 +545,8 @@ const sem = grammar.createSemantics().addAttribute('ast', {
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
? {
@@ -559,14 +559,14 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false,
pkce: pkceKey ? safeParseJson(pkceKey?.value) ?? false : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
? {
@@ -581,8 +581,8 @@ const sem = grammar.createSemantics().addAttribute('ast', {
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: {}
}

View File

@@ -1,6 +1,6 @@
const ohm = require('ohm-js');
const _ = require('lodash');
const { outdentString } = require('../../v1/src/utils');
const { safeParseJson, outdentString } = require('./utils');
const grammar = ohm.grammar(`Bru {
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
@@ -305,8 +305,8 @@ const sem = grammar.createSemantics().addAttribute('ast', {
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
? {
@@ -319,14 +319,14 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false,
pkce: pkceKey ? safeParseJson(pkceKey?.value) ?? false : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
? {
@@ -341,8 +341,8 @@ const sem = grammar.createSemantics().addAttribute('ast', {
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : 'Bearer',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
autoFetchToken: autoFetchTokenKey ? JSON.parse(autoFetchTokenKey?.value) : true,
autoRefreshToken: autoRefreshTokenKey ? JSON.parse(autoRefreshTokenKey?.value) : true
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? true : true
}
: {}
}

View File

@@ -1,6 +1,6 @@
const _ = require('lodash');
const { indentString } = require('../../v1/src/utils');
const { indentString } = require('./utils');
const enabled = (items = [], key = "enabled") => items.filter((item) => item[key]);
const disabled = (items = [], key = "enabled") => items.filter((item) => !item[key]);
@@ -196,8 +196,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken|| false).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken || false).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;
@@ -221,8 +221,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken|| false).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken || false).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;
@@ -242,8 +242,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken|| false).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken || false).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;

View File

@@ -1,6 +1,6 @@
const _ = require('lodash');
const { indentString } = require('../../v1/src/utils');
const { indentString } = require('./utils');
const enabled = (items = []) => items.filter((item) => item.enabled);
const disabled = (items = []) => items.filter((item) => !item.enabled);
@@ -162,8 +162,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;
@@ -187,8 +187,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;
@@ -208,8 +208,8 @@ ${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken).toString()}`)}
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? true).toString()}`)}
}
`;

View File

@@ -0,0 +1,36 @@
// safely parse json
const safeParseJson = (json) => {
try {
return JSON.parse(json);
} catch (e) {
return null;
}
};
const indentString = (str) => {
if (!str || !str.length) {
return str || '';
}
return str
.split('\n')
.map((line) => ' ' + line)
.join('\n');
};
const outdentString = (str) => {
if (!str || !str.length) {
return str || '';
}
return str
.split('\n')
.map((line) => line.replace(/^ /, ''))
.join('\n');
};
module.exports = {
safeParseJson,
indentString,
outdentString
};