diff --git a/packages/insomnia-app/app/models/request.js b/packages/insomnia-app/app/models/request.js index 13f2bc984f..259f9a72ff 100644 --- a/packages/insomnia-app/app/models/request.js +++ b/packages/insomnia-app/app/models/request.js @@ -85,7 +85,6 @@ type BaseRequest = { settingDisableRenderRequestBody: boolean, settingEncodeUrl: boolean, settingRebuildPath: boolean, - settingMaxTimelineDataSize: number, }; export type Request = BaseModel & BaseRequest; @@ -109,7 +108,6 @@ export function init(): BaseRequest { settingDisableRenderRequestBody: false, settingEncodeUrl: true, settingRebuildPath: true, - settingMaxTimelineDataSize: 1000, }; } diff --git a/packages/insomnia-app/app/models/response.js b/packages/insomnia-app/app/models/response.js index 9407722f6b..a1e6d287d2 100644 --- a/packages/insomnia-app/app/models/response.js +++ b/packages/insomnia-app/app/models/response.js @@ -37,8 +37,8 @@ type BaseResponse = { bytesContent: number, elapsedTime: number, headers: Array, - timeline: Array, bodyPath: string, // Actual bodies are stored on the filesystem + timelinePath: string, // Actual timelines are stored on the filesystem bodyCompression: 'zip' | null | '__NEEDS_MIGRATION__', error: string, requestVersionId: string | null, @@ -61,7 +61,7 @@ export function init(): BaseResponse { bytesContent: -1, // -1 means that it was legacy and this property didn't exist yet elapsedTime: 0, headers: [], - timeline: [], + timelinePath: '', // Actual timelines are stored on the filesystem bodyPath: '', // Actual bodies are stored on the filesystem bodyCompression: '__NEEDS_MIGRATION__', // For legacy bodies error: '', @@ -76,6 +76,7 @@ export function init(): BaseResponse { export async function migrate(doc: Object) { doc = await migrateBodyToFileSystem(doc); doc = await migrateBodyCompression(doc); + doc = await migrateTimelineToFileSystem(doc); return doc; } @@ -86,11 +87,12 @@ export async function hookDatabaseInit() { } export function hookRemove(doc: Response) { - if (!doc.bodyPath) { - return; - } - - fs.unlinkSync(doc.bodyPath); + fs.unlink(doc.bodyPath, () => { + console.log(`[response] Delete body ${doc.bodyPath}`); + }); + fs.unlink(doc.timelinePath, () => { + console.log(`[response] Delete timeline ${doc.timelinePath}`); + }); } export function getById(id: string) { @@ -160,6 +162,10 @@ export function getBodyBuffer(response: Object, readFailureValue: ?T): Buffer return getBodyBufferFromPath(response.bodyPath || '', response.bodyCompression, readFailureValue); } +export function getTimeline(response: Object): Array { + return getTimelineFromPath(response.timelinePath || ''); +} + function getBodyStreamFromPath( bodyPath: string, compression: string | null, @@ -208,11 +214,25 @@ function getBodyBufferFromPath( } } +function getTimelineFromPath(timelinePath: string): Array { + // No body, so return empty Buffer + if (!timelinePath) { + return []; + } + + try { + const rawBuffer = fs.readFileSync(timelinePath); + return JSON.parse(rawBuffer.toString()); + } catch (err) { + console.warn('Failed to read response body', err.message); + return []; + } +} + async function migrateBodyToFileSystem(doc: Object) { if (doc.hasOwnProperty('body') && doc._id && !doc.bodyPath) { const bodyBuffer = Buffer.from(doc.body, doc.encoding || 'utf8'); - const root = getDataDirectory(); - const dir = path.join(root, 'responses'); + const dir = path.join(getDataDirectory(), 'responses'); mkdirp.sync(dir); @@ -243,23 +263,46 @@ function migrateBodyCompression(doc: Object) { return doc; } +async function migrateTimelineToFileSystem(doc: Object) { + if (doc.hasOwnProperty('timeline') && doc._id && !doc.timelinePath) { + const dir = path.join(getDataDirectory(), 'responses'); + + mkdirp.sync(dir); + const timelineStr = JSON.stringify(doc.timeline, null, '\t'); + const fsPath = doc.bodyPath + '.timeline'; + + try { + fs.writeFileSync(fsPath, timelineStr); + } catch (err) { + console.warn('Failed to write response body to file', err.message); + } + + return db.docUpdate(doc, { timelinePath: fsPath }); + } else { + return doc; + } +} + export async function cleanDeletedResponses() { const responsesDir = path.join(getDataDirectory(), 'responses'); mkdirp.sync(responsesDir); - let files = fs.readdirSync(responsesDir); + const files = fs.readdirSync(responsesDir); if (files.length === 0) { return; } - let whitelistFiles = (await db.all(type)).map(res => { - return res.bodyPath.slice(responsesDir.length + 1); - }); + const whitelistFiles = []; + for (const r of await db.all(type)) { + whitelistFiles.push(r.bodyPath.slice(responsesDir.length + 1)); + whitelistFiles.push(r.timelinePath.slice(responsesDir.length + 1)); + } - for (let index = 0; index < files.length; index++) { - if (whitelistFiles.indexOf(files[index]) === -1) { - const bodyPath = path.join(responsesDir, files[index]); - fs.unlinkSync(bodyPath); + for (const filePath of files) { + if (whitelistFiles.indexOf(filePath) >= 0) { + continue; } + + fs.unlinkSync(path.join(responsesDir, filePath)); } } diff --git a/packages/insomnia-app/app/models/settings.js b/packages/insomnia-app/app/models/settings.js index f64f09fafd..f70c8cadde 100644 --- a/packages/insomnia-app/app/models/settings.js +++ b/packages/insomnia-app/app/models/settings.js @@ -36,6 +36,7 @@ type BaseSettings = { fontInterface: string | null, fontSize: number, fontVariantLigatures: boolean, + maxTimelineDataSizeKB: number, // Feature flags enableSyncBeta: boolean, @@ -83,6 +84,7 @@ export function init(): BaseSettings { fontInterface: null, fontSize: 13, fontVariantLigatures: false, + maxTimelineDataSizeKB: 10, enableSyncBeta: false, hotKeyRegistry: hotkeys.newDefaultRegistry(), }; diff --git a/packages/insomnia-app/app/network/network.js b/packages/insomnia-app/app/network/network.js index c9c045ecae..dcd8d47ce1 100644 --- a/packages/insomnia-app/app/network/network.js +++ b/packages/insomnia-app/app/network/network.js @@ -6,6 +6,7 @@ import type { Settings } from '../models/settings'; import type { RenderedRequest } from '../common/render'; import { getRenderedRequestAndContext, RENDER_PURPOSE_SEND } from '../common/render'; import mkdirp from 'mkdirp'; +import crypto from 'crypto'; import clone from 'clone'; import { parse as urlParse, resolve as urlResolve } from 'url'; import { Curl } from 'insomnia-libcurl'; @@ -56,7 +57,7 @@ import { urlMatchesCertHost } from './url-matches-cert-host'; import aws4 from 'aws4'; import { buildMultipart } from './multipart'; -export type ResponsePatch = { +export type ResponsePatch = {| statusMessage?: string, error?: string, url?: string, @@ -73,8 +74,8 @@ export type ResponsePatch = { parentId?: string, settingStoreCookies?: boolean, settingSendCookies?: boolean, - timeline?: Array, -}; + timelinePath?: string, +|}; // Time since user's last keypress to wait before making the request const MAX_DELAY_TIME = 1000; @@ -117,11 +118,13 @@ export async function _actuallySend( bodyPath: string | null, noPlugins: boolean = false, ): Promise { + const timelinePath = await storeTimeline(timeline); + const responsePatchBeforeHooks = Object.assign( ({ + timelinePath, parentId: renderedRequest._id, bodyCompression: null, // Will default to .zip otherwise - timeline: timeline, bodyPath: bodyPath || '', settingSendCookies: renderedRequest.settingSendCookies, settingStoreCookies: renderedRequest.settingStoreCookies, @@ -253,7 +256,7 @@ export async function _actuallySend( if (infoType === Curl.info.debug.DATA_OUT) { if (content.length === 0) { // Sometimes this happens, but I'm not sure why. Just ignore it. - } else if (content.length < renderedRequest.settingMaxTimelineDataSize) { + } else if (content.length / 1024 < settings.maxTimelineDataSizeKB) { addTimeline(name, content); } else { addTimeline(name, `(${describeByteSize(content.length)} hidden)`); @@ -991,6 +994,26 @@ export function _getAwsAuthHeaders( .map(name => ({ name, value: signature.headers[name] })); } +function storeTimeline(timeline: Array): Promise { + return new Promise((resolve, reject) => { + const timelineStr = JSON.stringify(timeline, null, '\t'); + const timelineHash = crypto + .createHash('sha1') + .update(timelineStr) + .digest('hex'); + const responsesDir = pathJoin(getDataDirectory(), 'responses'); + mkdirp.sync(responsesDir); + const timelinePath = pathJoin(responsesDir, timelineHash + '.timeline'); + fs.writeFile(timelinePath, timelineStr, err => { + if (err != null) { + reject(err); + } else { + resolve(timelinePath); + } + }); + }); +} + document.addEventListener('keydown', (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey || e.altKey) { return; diff --git a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js index 68b6c433f5..72e4127c76 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js +++ b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js @@ -313,67 +313,34 @@ class OneLineEditor extends PureComponent { if (showEditor) { return ( - {isVariableUncovered && ( - - )} - - {!isVariableUncovered && ( - - )} + ); } else { diff --git a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js index fe3d87410d..37c3752e93 100644 --- a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js +++ b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js @@ -590,56 +590,29 @@ class GraphQLEditor extends React.PureComponent { )}
- {isVariableUncovered && ( - Object.keys(variableTypes || {})} - lintOptions={{ - variableToType: variableTypes, - }} - nunjucksPowerUserMode={settings.nunjucksPowerUserMode} - isVariableUncovered={isVariableUncovered} - onChange={this._handleVariablesChange} - mode="graphql-variables" - lineWrapping={settings.editorLineWrapping} - placeholder="" - /> - )} - {!isVariableUncovered && ( - Object.keys(variableTypes || {})} - lintOptions={{ - variableToType: variableTypes, - }} - nunjucksPowerUserMode={settings.nunjucksPowerUserMode} - isVariableUncovered={isVariableUncovered} - onChange={this._handleVariablesChange} - mode="graphql-variables" - lineWrapping={settings.editorLineWrapping} - placeholder="" - /> - )} + Object.keys(variableTypes || {})} + lintOptions={{ + variableToType: variableTypes, + }} + nunjucksPowerUserMode={settings.nunjucksPowerUserMode} + isVariableUncovered={isVariableUncovered} + onChange={this._handleVariablesChange} + mode="graphql-variables" + lineWrapping={settings.editorLineWrapping} + placeholder="" + />