diff --git a/packages/bruno-app/src/components/FolderSettings/Auth/index.js b/packages/bruno-app/src/components/FolderSettings/Auth/index.js index e465d174d..360d5c64f 100644 --- a/packages/bruno-app/src/components/FolderSettings/Auth/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Auth/index.js @@ -46,11 +46,11 @@ const Auth = ({ collection, folder }) => { case 'oauth2': { return ( <> - diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 7bbc903b2..4c7e6029b 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -152,7 +152,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('script')}> Script {(script.req || script.res) && ( - item.preScriptResponseErrorMessage || item.postResponseScriptErrorMessage ? + item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ? : )} diff --git a/packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js new file mode 100644 index 000000000..8c32a8bab --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js @@ -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; diff --git a/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js new file mode 100644 index 000000000..18704cc41 --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js @@ -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 ( + + + + ); +}; + +export default ClearTimeline; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Time/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Time/index.js index d4fd0ec07..ab4cddce0 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Time/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Time/index.js @@ -27,7 +27,7 @@ export const RelativeTime = ({ timestamp }) => { useEffect(() => { const interval = setInterval(() => { setRelativeTime(getRelativeTime(new Date(timestamp))); - }, 1000); + }, 60000); return () => clearInterval(interval); }, [timestamp]); diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index 606c2043d..2d2a6ec25 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -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 ( @@ -134,11 +139,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { onClick={() => setShowScriptErrorCard(true)} /> )} - - - - - + {focusedTab?.responsePaneTab === "timeline" ? ( + + ) : item?.response ? ( + <> + + + + + + + ) : null}
) : null} @@ -152,7 +163,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { onClose={() => setShowScriptErrorCard(false)} /> )} - {getTabPanel(focusedTab.responsePaneTab)} + {!item?.response ? ( + focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? ( + + ) : null + ) : ( + <>{getTabPanel(focusedTab.responsePaneTab)} + )} ); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index e2551e7c6..ace2beefd 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -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); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 82691fd3e..d583fe261 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -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, diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js index 92f1513c8..ed9a46856 100644 --- a/packages/bruno-electron/src/utils/oauth2.js +++ b/packages/bruno-electron/src/utils/oauth2.js @@ -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; diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 301d99fd4..4cc36f557 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -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 } : {} } diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index 47046ba8b..d26580110 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -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 } : {} } diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 106cd706d..f9b5f84ed 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -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()}`)} } `; diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index 220686283..9ba1d90b4 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -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()}`)} } `; diff --git a/packages/bruno-lang/v2/src/utils.js b/packages/bruno-lang/v2/src/utils.js new file mode 100644 index 000000000..74b22c952 --- /dev/null +++ b/packages/bruno-lang/v2/src/utils.js @@ -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 +};