mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Update response-timeline-viewer to show response body. (#4346)
Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
This commit is contained in:
parent
8198992b68
commit
41a2fd6379
@ -466,3 +466,14 @@ export function isNotNullOrUndefined<ValueType>(
|
||||
}
|
||||
|
||||
export const kebabCase = (value: string) => value.replace(/ /g, '-');
|
||||
|
||||
// Because node-libcurl changed some names that we used in the timeline
|
||||
export const LIBCURL_DEBUG_MIGRATION_MAP = {
|
||||
HeaderIn: 'HEADER_IN',
|
||||
DataIn: 'DATA_IN',
|
||||
SslDataIn: 'SSL_DATA_IN',
|
||||
HeaderOut: 'HEADER_OUT',
|
||||
DataOut: 'DATA_OUT',
|
||||
SslDataOut: 'SSL_DATA_OUT',
|
||||
Text: 'TEXT',
|
||||
} as const;
|
||||
|
@ -3,10 +3,12 @@ import fs from 'fs';
|
||||
import mkdirp from 'mkdirp';
|
||||
import path from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import { ValueOf } from 'type-fest';
|
||||
import zlib from 'zlib';
|
||||
|
||||
import { database as db, Query } from '../common/database';
|
||||
import { getDataDirectory } from '../common/electron-helpers';
|
||||
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../common/misc';
|
||||
import type { BaseModel } from './index';
|
||||
import * as models from './index';
|
||||
|
||||
@ -26,7 +28,7 @@ export interface ResponseHeader {
|
||||
}
|
||||
|
||||
export interface ResponseTimelineEntry {
|
||||
name: string;
|
||||
name: ValueOf<typeof LIBCURL_DEBUG_MIGRATION_MAP>;
|
||||
timestamp: number;
|
||||
value: string;
|
||||
}
|
||||
@ -228,8 +230,30 @@ export const getBodyBuffer = <TFail = null>(
|
||||
readFailureValue,
|
||||
);
|
||||
|
||||
export function getTimeline(response: Response) {
|
||||
return getTimelineFromPath(response.timelinePath || '');
|
||||
export function getTimeline(response: Response, showBody?: boolean) {
|
||||
const { timelinePath, bodyPath } = response;
|
||||
|
||||
// No body, so return empty Buffer
|
||||
if (!timelinePath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const rawBuffer = fs.readFileSync(timelinePath);
|
||||
const timeline = JSON.parse(rawBuffer.toString()) as ResponseTimelineEntry[];
|
||||
const body: ResponseTimelineEntry[] = showBody ? [
|
||||
{
|
||||
name: LIBCURL_DEBUG_MIGRATION_MAP.DataOut,
|
||||
timestamp: Date.now(),
|
||||
value: fs.readFileSync(bodyPath).toString(),
|
||||
},
|
||||
] : [];
|
||||
const output = [...timeline, ...body];
|
||||
return output;
|
||||
} catch (err) {
|
||||
console.warn('Failed to read response body', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getBodyStreamFromPath<TFail extends Readable>(
|
||||
@ -282,21 +306,6 @@ function getBodyBufferFromPath<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function getTimelineFromPath(timelinePath: string) {
|
||||
// No body, so return empty Buffer
|
||||
if (!timelinePath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const rawBuffer = fs.readFileSync(timelinePath);
|
||||
return JSON.parse(rawBuffer.toString()) as ResponseTimelineEntry[];
|
||||
} catch (err) {
|
||||
console.warn('Failed to read response body', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateBodyToFileSystem(doc: Response) {
|
||||
if (doc.hasOwnProperty('body') && doc._id && !doc.bodyPath) {
|
||||
// @ts-expect-error -- TSCONVERSION previously doc.body and doc.encoding did exist but are now removed, and if they exist we want to migrate away from them
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
hasAuthHeader,
|
||||
hasContentTypeHeader,
|
||||
hasUserAgentHeader,
|
||||
LIBCURL_DEBUG_MIGRATION_MAP,
|
||||
waitForStreamToFinish,
|
||||
} from '../common/misc';
|
||||
import type { ExtraRenderInfo, RenderedRequest } from '../common/render';
|
||||
@ -96,18 +97,6 @@ const MAX_DELAY_TIME = 1000;
|
||||
// Special header value that will prevent the header being sent
|
||||
const DISABLE_HEADER_VALUE = '__Di$aB13d__';
|
||||
|
||||
// Because node-libcurl changed some names that we used in the timeline
|
||||
const LIBCURL_DEBUG_MIGRATION_MAP = {
|
||||
HeaderIn: 'HEADER_IN',
|
||||
DataIn: 'DATA_IN',
|
||||
SslDataIn: 'SSL_DATA_IN',
|
||||
HeaderOut: 'HEADER_OUT',
|
||||
DataOut: 'DATA_OUT',
|
||||
SslDataOut: 'SSL_DATA_OUT',
|
||||
Text: 'TEXT',
|
||||
'': '',
|
||||
};
|
||||
|
||||
const cancelRequestFunctionMap = {};
|
||||
|
||||
let lastUserInteraction = Date.now();
|
||||
@ -163,17 +152,15 @@ export async function _actuallySend(
|
||||
return new Promise<ResponsePatch>(async resolve => {
|
||||
const timeline: ResponseTimelineEntry[] = [];
|
||||
|
||||
function addTimeline(name, value) {
|
||||
const addTimelineItem = (name: ResponseTimelineEntry['name']) => (value: string) => {
|
||||
timeline.push({
|
||||
name,
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function addTimelineText(value) {
|
||||
addTimeline('TEXT', value);
|
||||
}
|
||||
const addTimelineText = addTimelineItem(LIBCURL_DEBUG_MIGRATION_MAP.Text);
|
||||
|
||||
// Initialize the curl handle
|
||||
const curl = new Curl();
|
||||
@ -310,6 +297,7 @@ export async function _actuallySend(
|
||||
const content = contentBuffer.toString('utf8');
|
||||
const rawName = Object.keys(CurlInfoDebug).find(k => CurlInfoDebug[k] === infoType) || '';
|
||||
const name = LIBCURL_DEBUG_MIGRATION_MAP[rawName] || rawName;
|
||||
const addToTimeline = addTimelineItem(name);
|
||||
|
||||
if (infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut) {
|
||||
return 0;
|
||||
@ -320,9 +308,9 @@ export async function _actuallySend(
|
||||
if (contentBuffer.length === 0) {
|
||||
// Sometimes this happens, but I'm not sure why. Just ignore it.
|
||||
} else if (contentBuffer.length / 1024 < settings.maxTimelineDataSizeKB) {
|
||||
addTimeline(name, content);
|
||||
addToTimeline(content);
|
||||
} else {
|
||||
addTimeline(name, `(${describeByteSize(contentBuffer.length)} hidden)`);
|
||||
addToTimeline(`(${describeByteSize(contentBuffer.length)} hidden)`);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -338,9 +326,10 @@ export async function _actuallySend(
|
||||
return 0;
|
||||
}
|
||||
|
||||
addTimeline(name, content);
|
||||
addToTimeline(content);
|
||||
return 0; // Must be here
|
||||
});
|
||||
|
||||
// Set the headers (to be modified as we go)
|
||||
const headers = clone(renderedRequest.headers);
|
||||
// Set the URL, including the query parameters
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Button } from 'insomnia-components';
|
||||
import React, { ChangeEvent, FC, ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@ -378,17 +379,18 @@ const OAuth2Error: FC = () => {
|
||||
|
||||
showModal(ResponseDebugModal, {
|
||||
responseId: token.xResponseId,
|
||||
showBody: true,
|
||||
});
|
||||
};
|
||||
|
||||
const debugButton = token?.xResponseId ? (
|
||||
<button
|
||||
<Button
|
||||
onClick={debug}
|
||||
className="icon icon--success space-left"
|
||||
className="margin-top-sm"
|
||||
title="View response timeline"
|
||||
>
|
||||
<i className="fa fa-bug" />
|
||||
</button>
|
||||
<i className="fa fa-bug space-right" /> Response Timeline
|
||||
</Button>
|
||||
) : null;
|
||||
|
||||
const errorUriButton = token?.errorUri ? (
|
||||
@ -407,8 +409,8 @@ const OAuth2Error: FC = () => {
|
||||
<p>
|
||||
{errorDescription || 'no description provided'}
|
||||
{errorUriButton}
|
||||
{debugButton}
|
||||
</p>
|
||||
{debugButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { ModalHeader } from '../base/modal-header';
|
||||
|
||||
interface State {
|
||||
response: Response | null;
|
||||
showBody?: boolean;
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
@ -20,6 +21,7 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
|
||||
|
||||
state: State = {
|
||||
response: null,
|
||||
showBody: false,
|
||||
title: '',
|
||||
};
|
||||
|
||||
@ -31,19 +33,20 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
async show(options: { responseId?: string; response?: Response; title?: string }) {
|
||||
async show(options: { responseId?: string; response?: Response; title?: string; showBody?: boolean }) {
|
||||
const response = options.response
|
||||
? options.response
|
||||
: await models.response.getById(options.responseId || 'n/a');
|
||||
this.setState({
|
||||
response,
|
||||
title: options.title || null,
|
||||
showBody: options.showBody,
|
||||
});
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { response, title } = this.state;
|
||||
const { response, title, showBody } = this.state;
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall>
|
||||
<ModalHeader>{title || 'Response Timeline'}</ModalHeader>
|
||||
@ -57,6 +60,7 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
|
||||
{response ? (
|
||||
<ResponseTimelineViewer
|
||||
response={response}
|
||||
showBody={showBody}
|
||||
/>
|
||||
) : (
|
||||
<div>No response found</div>
|
||||
|
@ -134,7 +134,7 @@ export class ResponsePane extends PureComponent<Props> {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeline = await models.response.getTimeline(response);
|
||||
const timeline = models.response.getTimeline(response);
|
||||
const headers = timeline
|
||||
.filter(v => v.name === 'HEADER_IN')
|
||||
.map(v => v.value)
|
||||
|
@ -1,16 +1,18 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { clickLink } from '../../../common/electron-helpers';
|
||||
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../../../common/misc';
|
||||
import * as models from '../../../models';
|
||||
import { Response } from '../../../models/response';
|
||||
import { Response, ResponseTimelineEntry } from '../../../models/response';
|
||||
import { CodeEditor } from '../codemirror/code-editor';
|
||||
|
||||
interface Props {
|
||||
showBody?: boolean;
|
||||
response: Response;
|
||||
}
|
||||
|
||||
interface State {
|
||||
timeline: any[];
|
||||
timeline: ResponseTimelineEntry[];
|
||||
timelineKey: string;
|
||||
}
|
||||
|
||||
@ -33,45 +35,46 @@ export class ResponseTimelineViewer extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async refreshTimeline() {
|
||||
const { response } = this.props;
|
||||
const timeline = await models.response.getTimeline(response);
|
||||
const { response, showBody } = this.props;
|
||||
const timeline = models.response.getTimeline(response, showBody);
|
||||
|
||||
this.setState({
|
||||
timeline,
|
||||
timelineKey: response._id,
|
||||
});
|
||||
}
|
||||
|
||||
renderRow(row, i, all) {
|
||||
renderRow(row: ResponseTimelineEntry, i: number, all: ResponseTimelineEntry[]) {
|
||||
const { name, value } = row;
|
||||
const previousName = i > 0 ? all[i - 1].name : '';
|
||||
let prefix: string | null = null;
|
||||
|
||||
switch (name) {
|
||||
case 'HEADER_IN':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderIn:
|
||||
prefix = '< ';
|
||||
break;
|
||||
|
||||
case 'DATA_IN':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.DataIn:
|
||||
prefix = '| ';
|
||||
break;
|
||||
|
||||
case 'SSL_DATA_IN':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataIn:
|
||||
prefix = '<< ';
|
||||
break;
|
||||
|
||||
case 'HEADER_OUT':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderOut:
|
||||
prefix = '> ';
|
||||
break;
|
||||
|
||||
case 'DATA_OUT':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.DataOut:
|
||||
prefix = '| ';
|
||||
break;
|
||||
|
||||
case 'SSL_DATA_OUT':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataOut:
|
||||
prefix = '>> ';
|
||||
break;
|
||||
|
||||
case 'TEXT':
|
||||
case LIBCURL_DEBUG_MIGRATION_MAP.Text:
|
||||
prefix = '* ';
|
||||
break;
|
||||
|
||||
@ -99,6 +102,7 @@ export class ResponseTimelineViewer extends PureComponent<Props, State> {
|
||||
.filter(r => r !== null)
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<CodeEditor
|
||||
key={timelineKey}
|
||||
|
Loading…
Reference in New Issue
Block a user