Merge pull request #4300 from lohxt1/oauth2_fixes

timeline ui fixes, oauth2 validation fixes
This commit is contained in:
lohit
2025-03-21 15:58:32 +05:30
committed by GitHub
5 changed files with 201 additions and 111 deletions

View File

@@ -91,7 +91,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
// Always show raw
const allowedPreviewModes = [{ mode: 'raw', name: 'Raw', uid: uuid() }];
if (!mode || !contentType) return;
if (!mode || !contentType) return allowedPreviewModes;
if (mode?.includes('html') && typeof data === 'string') {
allowedPreviewModes.unshift({ mode: 'preview-web', name: 'Web', uid: uuid() });

View File

@@ -101,28 +101,28 @@ function makeAxiosInstance({
const url = URL.parse(config.url);
config.metadata = config.metadata || {};
config.metadata.startTime = new Date().getTime();
config.metadata.timeline = config.metadata.timeline || [];
const timeline = config.metadata.timeline || []
// Add initial request details to the timeline
config.metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Preparing request to ${config.url}`,
});
config.metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Current time is ${new Date().toISOString()}`,
});
// Add request method and headers
config.metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'request',
message: `${config.method.toUpperCase()} ${config.url}`,
});
Object.entries(config.headers).forEach(([key, value]) => {
config.metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'requestHeader',
message: `${key}: ${value}`,
@@ -131,13 +131,8 @@ function makeAxiosInstance({
// Add request data if available
if (config.data) {
let requestData;
try {
requestData = typeof config.data === 'string' ? config.data : JSON.stringify(config.data, null, 2);
} catch (err) {
requestData = config.data.toString();
}
config.metadata.timeline.push({
let requestData = typeof config.data === 'string' ? config.data : JSON.stringify(config.data, null, 2);
timeline.push({
timestamp: new Date(),
type: 'requestData',
message: requestData,
@@ -164,16 +159,26 @@ function makeAxiosInstance({
...httpsAgentRequestFields,
keepAlive: true,
};
// Now call setupProxyAgents and pass the timeline
setupProxyAgents({
requestConfig: config,
proxyMode: proxyMode, // 'on', 'off', or 'system', depending on your settings
proxyConfig: proxyConfig,
httpsAgentRequestFields: agentOptions,
interpolationOptions: interpolationOptions, // Provide your interpolation options
timeline: config.metadata.timeline,
});
try {
// Now call setupProxyAgents and pass the timeline
setupProxyAgents({
requestConfig: config,
proxyMode: proxyMode, // 'on', 'off', or 'system', depending on your settings
proxyConfig: proxyConfig,
httpsAgentRequestFields: agentOptions,
interpolationOptions: interpolationOptions, // Provide your interpolation options
timeline,
});
}
catch(err) {
timeline.push({
timestamp: new Date(),
type: 'error',
message: err?.message,
});
}
config.metadata.timeline = timeline;
return config;
});
@@ -181,93 +186,95 @@ function makeAxiosInstance({
instance.interceptors.response.use(
(response) => {
let timeline;
const end = Date.now();
const start = response.config.headers['request-start-time'];
response.headers['request-duration'] = end - start;
redirectCount = 0;
const config = response.config;
const metadata = config.metadata;
const duration = end - metadata.startTime;
timeline = config?.metadata?.timeline || []
const duration = end - config?.metadata.startTime;
const httpVersion = response.request?.res?.httpVersion || '1.1';
metadata.timeline.push({
timestamp: new Date(),
type: 'response',
message: `HTTP/${httpVersion} ${response.status} ${response.statusText}`,
});
if (httpVersion.startsWith('2')) {
metadata.timeline.push({
const httpVersion = response?.request?.res?.httpVersion || response?.httpVersion;
if (httpVersion?.startsWith('2')) {
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Using HTTP/2, server supports multiplexing`,
});
}
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'response',
message: `HTTP/${response.httpVersion || '1.1'} ${response.status} ${response.statusText}`,
message: `HTTP/${httpVersion || '1.1'} ${response.status} ${response.statusText}`,
});
Object.entries(response.headers).forEach(([key, value]) => {
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'responseHeader',
message: `${key}: ${value}`,
});
});
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Request completed in ${duration} ms`,
});
// Attach the timeline to the response
response.timeline = metadata.timeline;
response.timeline = timeline;
return response;
},
(error) => {
const config = error.config;
const timeline = config?.metadata?.timeline || [];
timeline?.push({
timestamp: new Date(),
type: 'error',
message: 'there was an error executing the request!'
});
if (error.response) {
const end = Date.now();
const start = error.config.headers['request-start-time'];
error.response.headers['request-duration'] = end - start;
const config = error.config;
const metadata = config.metadata;
const duration = end - metadata.startTime;
const duration = end - config?.metadata?.startTime;
if (error.response && redirectResponseCodes.includes(error.response.status)) {
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'response',
message: `HTTP/${error.response.httpVersion || '1.1'} ${error.response.status} ${error.response.statusText}`,
});
Object.entries(error.response.headers).forEach(([key, value]) => {
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'responseHeader',
message: `${key}: ${value}`,
});
});
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Request completed in ${duration} ms`,
});
// Attach the timeline to the response
error.response.timeline = metadata.timeline;
error.response.timeline = timeline;
if (redirectCount >= requestMaxRedirects) {
const dataBuffer = Buffer.from(error.response.data);
const errorResponseData = error.response.data;
const dataBuffer = Buffer.isBuffer(errorResponseData) ? errorResponseData : Buffer.from(errorResponseData);
timeline?.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(errorResponseData?.toString?.())
});
return {
status: error.response.status,
statusText: error.response.statusText,
headers: error.response.headers,
data: error.response.data,
dataBuffer: dataBuffer.toString('base64'),
data: errorResponseData?.toString?.(),
dataBuffer: dataBuffer,
size: Buffer.byteLength(dataBuffer),
duration: error.response.headers.get('request-duration') ?? 0,
timeline: error.response.timeline
@@ -285,7 +292,7 @@ function makeAxiosInstance({
// It's a relative URL, resolve it against the original URL
redirectUrl = URL.resolve(error.config.url, locationHeader);
metadata.timeline.push({
timeline.push({
timestamp: new Date(),
type: 'info',
message: `Resolving relative redirect URL: ${locationHeader}${redirectUrl}`,
@@ -318,26 +325,74 @@ function makeAxiosInstance({
proxyConfig,
httpsAgentRequestFields,
interpolationOptions,
timeline: metadata.timeline
timeline
});
requestConfig.metadata.timeline = timeline;
// Make the redirected request
return instance(requestConfig);
}
else {
const errorResponseData = error.response.data;
const dataBuffer = Buffer.isBuffer(errorResponseData) ? errorResponseData : Buffer.from(errorResponseData);
Object.entries(error?.response?.headers || {}).forEach(([key, value]) => {
timeline.push({
timestamp: new Date(),
type: 'responseHeader',
message: `${key}: ${value}`,
});
});
timeline?.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(errorResponseData?.toString?.())
});
error?.cause && timeline?.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(error?.cause)
});
error?.errors && timeline?.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(error?.errors)
});
return {
status: error.response.status,
statusText: error.response.statusText,
headers: error.response.headers,
data: errorResponseData?.toString?.(),
dataBuffer: dataBuffer,
size: Buffer.byteLength(dataBuffer),
duration: error.response.headers.get('request-duration') ?? 0,
timeline
};
}
}
else if (error?.code) {
let metadata = error?.config?.metadata;
metadata?.timeline?.push({
Object.entries(error?.response?.headers || {}).forEach(([key, value]) => {
timeline.push({
timestamp: new Date(),
type: 'responseHeader',
message: `${key}: ${value}`,
});
});
timeline?.push({
timestamp: new Date(),
type: 'error',
message: `${safeStringifyJSON(error?.cause) || ''}\n${safeStringifyJSON(error?.errors) || ''}`
message: safeStringifyJSON(error?.cause)
});
timeline?.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(error?.errors)
});
return {
status: '-',
statusText: error.code,
headers: error?.config?.headers,
data: 'request failed, check timeline network logs',
timeline: metadata.timeline
timeline
};
}
return Promise.reject(error);

View File

@@ -3,7 +3,6 @@ const https = require('https');
const axios = require('axios');
const path = require('path');
const decomment = require('decomment');
const iconv = require('iconv-lite');
const fs = require('fs');
const tls = require('tls');
const contentDispositionParser = require('content-disposition');
@@ -21,7 +20,7 @@ const { prepareRequest } = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
const { makeAxiosInstance } = require('./axios-instance');
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
const { uuid, safeStringifyJSON, safeParseJSON } = require('../../utils/common');
const { uuid, safeStringifyJSON, safeParseJSON, parseDataFromResponse } = require('../../utils/common');
const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
const { addCookieToJar, getDomainsWithCookies, getCookieStringForUrl } = require('../../utils/cookies');
const { createFormData } = require('../../utils/form-data');
@@ -328,33 +327,6 @@ const configureRequest = async (
return axiosInstance;
};
const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
const charsetValue = charsetMatch?.[1];
const dataBuffer = Buffer.from(response.data);
// Overwrite the original data for backwards compatibility
let data;
if (iconv.encodingExists(charsetValue)) {
data = iconv.decode(dataBuffer, charsetValue);
} else {
data = iconv.decode(dataBuffer, 'utf-8');
}
// Try to parse response to JSON, this can quietly fail
try {
// Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, '');
if (!disableParsingResponseJson) {
data = JSON.parse(data);
}
} catch { }
return { data, dataBuffer };
};
const registerNetworkIpc = (mainWindow) => {
const onConsoleLog = (type, args) => {
console[type](...args);

View File

@@ -1,4 +1,5 @@
const { customAlphabet } = require('nanoid');
const iconv = require('iconv-lite');
// a customized version of nanoid without using _ and -
const uuid = () => {
@@ -85,6 +86,32 @@ const flattenDataForDotNotation = (data) => {
return result;
};
const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
const charsetValue = charsetMatch?.[1];
const dataBuffer = Buffer.from(response.data);
// Overwrite the original data for backwards compatibility
let data;
if (iconv.encodingExists(charsetValue)) {
data = iconv.decode(dataBuffer, charsetValue);
} else {
data = iconv.decode(dataBuffer, 'utf-8');
}
// Try to parse response to JSON, this can quietly fail
try {
// Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, '');
if (!disableParsingResponseJson) {
data = JSON.parse(data);
}
} catch { }
return { data, dataBuffer };
};
module.exports = {
uuid,
stringifyJson,
@@ -93,5 +120,6 @@ module.exports = {
safeParseJSON,
simpleHash,
generateUidBasedOnHash,
flattenDataForDotNotation
flattenDataForDotNotation,
parseDataFromResponse
};

View File

@@ -163,6 +163,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
headers: response.headers,
data: response.data,
timestamp: Date.now(),
timeline: response?.timeline
};
return response;
}, (error) => {
@@ -174,6 +175,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
headers: error.response.headers,
data: error.response.data,
timestamp: Date.now(),
timeline: error?.response?.timeline,
error: 'fetching access token failed! check timeline network logs'
};
}
@@ -182,7 +184,8 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
status: '-',
statusText: error.code,
headers: error?.config?.headers,
data: safeStringifyJSON(error?.errors)
data: safeStringifyJSON(error?.errors),
timeline: error?.response?.timeline
};
}
return axiosResponseInfo;
@@ -190,7 +193,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
const response = await axiosInstance(requestCopy);
const parsedResponseData = safeParseJSON(
Buffer.isBuffer(response.data) ? response.data.toString() : response.data
Buffer.isBuffer(response.data) ? response.data?.toString() : response.data
);
// Ensure debugInfo.data is initialized
if (!debugInfo) {
@@ -217,7 +220,8 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
dataBuffer: axiosResponseInfo?.data,
status: axiosResponseInfo?.status,
statusText: axiosResponseInfo?.statusText,
error: axiosResponseInfo?.error
error: axiosResponseInfo?.error,
timeline: axiosResponseInfo?.timeline
},
fromCache: false,
completed: true,
@@ -365,13 +369,14 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
try {
const axiosInstance = makeAxiosInstance();
// Interceptor to capture request data
axiosInstance.interceptors.request.use((config) => {
const requestData = typeof config?.data === 'string' ? config?.data : safeStringifyJSON(config?.data);
axiosRequestInfo = {
method: config.method.toUpperCase(),
url: config.url,
headers: config.headers,
data: config.data,
data: requestData,
dataBuffer: Buffer.from(requestData),
timestamp: Date.now(),
};
return config;
@@ -380,12 +385,13 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
// Interceptor to capture response data
axiosInstance.interceptors.response.use((response) => {
axiosResponseInfo = {
url: response.url,
url: response?.url,
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
timestamp: Date.now(),
timeline: response?.timeline
};
return response;
}, (error) => {
@@ -397,14 +403,26 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
headers: error.response.headers,
data: error.response.data,
timestamp: Date.now(),
timeline: error?.response?.timeline,
error: 'fetching access token failed! check timeline network logs'
};
}
return Promise.reject(error);
else if(error?.code) {
axiosResponseInfo = {
status: '-',
statusText: error.code,
headers: error?.config?.headers,
data: safeStringifyJSON(error?.errors),
timeline: error?.response?.timeline
};
}
return axiosResponseInfo;
});
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
const parsedResponseData = safeParseJSON(
Buffer.isBuffer(response.data) ? response.data.toString() : response.data
);
// Add the axios request and response info as a main request in debugInfo
const axiosMainRequest = {
@@ -413,16 +431,18 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
url: axiosRequestInfo?.url,
method: axiosRequestInfo?.method,
headers: axiosRequestInfo?.headers || {},
body: axiosRequestInfo?.data,
data: axiosRequestInfo?.data,
dataBuffer: axiosRequestInfo?.dataBuffer,
error: null
},
response: {
url: axiosResponseInfo.url,
headers: axiosResponseInfo?.headers,
data: parsedResponseData,
dataBuffer: axiosResponseInfo?.data?.toString('base64'),
dataBuffer: axiosResponseInfo?.data,
status: axiosResponseInfo?.status,
statusText: axiosResponseInfo?.statusText,
timeline: axiosResponseInfo?.timeline,
error: null
},
fromCache: false,
@@ -539,13 +559,14 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
try {
const axiosInstance = makeAxiosInstance();
// Interceptor to capture request data
axiosInstance.interceptors.request.use((config) => {
const requestData = typeof config?.data === 'string' ? config?.data : safeStringifyJSON(config?.data);
axiosRequestInfo = {
method: config.method.toUpperCase(),
url: config.url,
headers: config.headers,
data: config.data,
data: requestData,
dataBuffer: Buffer.from(requestData),
timestamp: Date.now(),
};
return config;
@@ -554,12 +575,13 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
// Interceptor to capture response data
axiosInstance.interceptors.response.use((response) => {
axiosResponseInfo = {
url: response.url,
url: response?.url,
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
timestamp: Date.now(),
timeline: response?.timeline
};
return response;
}, (error) => {
@@ -571,14 +593,25 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
headers: error.response.headers,
data: error.response.data,
timestamp: Date.now(),
timeline: error?.response?.timeline,
error: 'fetching access token failed! check timeline network logs'
};
}
return Promise.reject(error);
else if(error?.code) {
axiosResponseInfo = {
status: '-',
statusText: error.code,
headers: error?.config?.headers,
data: safeStringifyJSON(error?.errors),
timeline: error?.response?.timeline
};
}
return axiosResponseInfo;
});
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
const parsedResponseData = safeParseJSON(
Buffer.isBuffer(response.data) ? response.data.toString() : response.data
);
// Add the axios request and response info as a main request in debugInfo
const axiosMainRequest = {
@@ -587,16 +620,18 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
url: axiosRequestInfo?.url,
method: axiosRequestInfo?.method,
headers: axiosRequestInfo?.headers || {},
body: axiosRequestInfo?.data,
data: axiosRequestInfo?.data,
dataBuffer: axiosRequestInfo?.dataBuffer,
error: null
},
response: {
url: axiosResponseInfo?.url,
headers: axiosResponseInfo?.headers,
data: parsedResponseData,
dataBuffer: axiosResponseInfo?.data?.toString('base64'),
dataBuffer: axiosResponseInfo?.data,
status: axiosResponseInfo?.status,
statusText: axiosResponseInfo?.statusText,
timeline: axiosResponseInfo?.timeline,
error: null
},
fromCache: false,