insomnia/packages/insomnia-app/app/models/response.js

352 lines
9.6 KiB
JavaScript
Raw Normal View History

2017-07-18 22:10:57 +00:00
// @flow
2018-06-25 17:42:50 +00:00
import type { BaseModel } from './index';
2017-07-19 04:48:28 +00:00
import * as models from './index';
2018-06-25 17:42:50 +00:00
import { Readable } from 'stream';
2017-07-19 04:48:28 +00:00
import fs from 'fs';
import crypto from 'crypto';
import path from 'path';
import zlib from 'zlib';
import mkdirp from 'mkdirp';
import * as db from '../common/database';
import { getDataDirectory } from '../common/misc';
2016-09-21 20:32:45 +00:00
export const name = 'Response';
export const type = 'Response';
export const prefix = 'res';
export const canDuplicate = false;
Version Control (beta) (#1439) * VCS proof of concept underway! * Stuff * Some things * Replace deprecated Electron makeSingleInstance * Rename `window` variables so not to be confused with window object * Don't unnecessarily update request when URL does not change * Regenerate package-lock * Fix tests + ESLint * Publish - insomnia-app@1.0.49 - insomnia-cookies@0.0.12 - insomnia-httpsnippet@1.16.18 - insomnia-importers@2.0.13 - insomnia-libcurl@0.0.23 - insomnia-prettify@0.1.7 - insomnia-url@0.1.6 - insomnia-xpath@1.0.9 - insomnia-plugin-base64@1.0.6 - insomnia-plugin-cookie-jar@1.0.8 - insomnia-plugin-core-themes@1.0.5 - insomnia-plugin-default-headers@1.1.9 - insomnia-plugin-file@1.0.7 - insomnia-plugin-hash@1.0.7 - insomnia-plugin-jsonpath@1.0.12 - insomnia-plugin-now@1.0.11 - insomnia-plugin-os@1.0.13 - insomnia-plugin-prompt@1.1.9 - insomnia-plugin-request@1.0.18 - insomnia-plugin-response@1.0.16 - insomnia-plugin-uuid@1.0.10 * Broken but w/e * Some tweaks * Big refactor. Create local snapshots and push done * POC merging and a lot of improvements * Lots of work done on initial UI/UX * Fix old tests * Atomic writes and size-based batches * Update StageEntry definition once again to be better * Factor out GraphQL query logic * Merge algorithm, history modal, other minor things * Fix test * Merge, checkout, revert w/ user changes now work * Force UI to refresh when switching branches changes active request * Rough draft pull() and some cleanup * E2EE stuff and some refactoring * Add ability to share project with team and fixed tests * VCS now created in root component and better remote project handling * Remove unused definition * Publish - insomnia-account@0.0.2 - insomnia-app@1.1.1 - insomnia-cookies@0.0.14 - insomnia-httpsnippet@1.16.20 - insomnia-importers@2.0.15 - insomnia-libcurl@0.0.25 - insomnia-prettify@0.1.9 - insomnia-sync@0.0.2 - insomnia-url@0.1.8 - insomnia-xpath@1.0.11 - insomnia-plugin-base64@1.0.8 - insomnia-plugin-cookie-jar@1.0.10 - insomnia-plugin-core-themes@1.0.7 - insomnia-plugin-file@1.0.9 - insomnia-plugin-hash@1.0.9 - insomnia-plugin-jsonpath@1.0.14 - insomnia-plugin-now@1.0.13 - insomnia-plugin-os@1.0.15 - insomnia-plugin-prompt@1.1.11 - insomnia-plugin-request@1.0.20 - insomnia-plugin-response@1.0.18 - insomnia-plugin-uuid@1.0.12 * Move some deps around * Fix Flow errors * Update package.json * Fix eslint errors * Fix tests * Update deps * bootstrap insomnia-sync * TRy fixing appveyor * Try something else * Bump lerna * try powershell * Try again * Fix imports * Fixed errors * sync types refactor * Show remote projects in workspace dropdown * Improved pulling of non-local workspaces * Loading indicators and some tweaks * Clean up sync staging modal * Some sync improvements: - No longer store stage - Upgrade Electron - Sync UI/UX improvements * Fix snyc tests * Upgraded deps and hot loader tweaks (it's broken for some reason) * Fix tests * Branches dialog, network refactoring, some tweaks * Fixed merging when other branch is empty * A bunch of small fixes from real testing * Fixed pull merge logic * Fix tests * Some bug fixes * A few small tweaks * Conflict resolution and other improvements * Fix tests * Add revert changes * Deal with duplicate projects per workspace * Some tweaks and accessibility improvements * Tooltip accessibility * Fix API endpoint * Fix tests * Remove jest dep from insomnia-importers
2019-04-18 00:50:03 +00:00
export const canSync = false;
2017-07-18 22:10:57 +00:00
export type ResponseHeader = {
name: string,
value: string,
2018-06-25 17:42:50 +00:00
};
2017-07-18 22:10:57 +00:00
export type ResponseTimelineEntry = {
name: string,
value: string,
2018-06-25 17:42:50 +00:00
};
2017-07-18 22:10:57 +00:00
2017-07-19 04:48:28 +00:00
type BaseResponse = {
environmentId: string | null,
2017-07-18 22:10:57 +00:00
statusCode: number,
statusMessage: string,
2017-11-13 23:10:53 +00:00
httpVersion: string,
2017-07-18 22:10:57 +00:00
contentType: string,
url: string,
bytesRead: number,
bytesContent: number,
2017-07-18 22:10:57 +00:00
elapsedTime: number,
headers: Array<ResponseHeader>,
bodyPath: string, // Actual bodies are stored on the filesystem
timelinePath: string, // Actual timelines are stored on the filesystem
2017-11-28 11:25:05 +00:00
bodyCompression: 'zip' | null | '__NEEDS_MIGRATION__',
2017-07-18 22:10:57 +00:00
error: string,
requestVersionId: string | null,
// Things from the request
settingStoreCookies: boolean | null,
settingSendCookies: boolean | null,
2017-07-18 22:10:57 +00:00
};
2017-07-19 04:48:28 +00:00
export type Response = BaseModel & BaseResponse;
2018-06-25 17:42:50 +00:00
export function init(): BaseResponse {
2016-11-10 01:15:27 +00:00
return {
statusCode: 0,
statusMessage: '',
2017-11-13 23:10:53 +00:00
httpVersion: '',
2016-10-27 16:45:44 +00:00
contentType: '',
url: '',
bytesRead: 0,
bytesContent: -1, // -1 means that it was legacy and this property didn't exist yet
elapsedTime: 0,
headers: [],
timelinePath: '', // Actual timelines are stored on the filesystem
bodyPath: '', // Actual bodies are stored on the filesystem
2017-11-28 11:25:05 +00:00
bodyCompression: '__NEEDS_MIGRATION__', // For legacy bodies
error: '',
requestVersionId: null,
// Things from the request
settingStoreCookies: null,
settingSendCookies: null,
// Responses sent before environment filtering will have a special value
// so they don't show up at all when filtering is on.
environmentId: '__LEGACY__',
};
}
2016-09-21 20:32:45 +00:00
2018-06-25 17:42:50 +00:00
export async function migrate(doc: Object) {
2017-11-28 11:25:05 +00:00
doc = await migrateBodyToFileSystem(doc);
doc = await migrateBodyCompression(doc);
doc = await migrateTimelineToFileSystem(doc);
return doc;
}
export function hookDatabaseInit() {
console.log('Init responses DB');
process.nextTick(async () => {
await models.response.cleanDeletedResponses();
});
}
2018-06-25 17:42:50 +00:00
export function hookRemove(doc: Response) {
fs.unlink(doc.bodyPath, () => {
console.log(`[response] Delete body ${doc.bodyPath}`);
});
fs.unlink(doc.timelinePath, () => {
console.log(`[response] Delete timeline ${doc.timelinePath}`);
});
}
2018-06-25 17:42:50 +00:00
export function getById(id: string) {
return db.get(type, id);
}
Version Control (beta) (#1439) * VCS proof of concept underway! * Stuff * Some things * Replace deprecated Electron makeSingleInstance * Rename `window` variables so not to be confused with window object * Don't unnecessarily update request when URL does not change * Regenerate package-lock * Fix tests + ESLint * Publish - insomnia-app@1.0.49 - insomnia-cookies@0.0.12 - insomnia-httpsnippet@1.16.18 - insomnia-importers@2.0.13 - insomnia-libcurl@0.0.23 - insomnia-prettify@0.1.7 - insomnia-url@0.1.6 - insomnia-xpath@1.0.9 - insomnia-plugin-base64@1.0.6 - insomnia-plugin-cookie-jar@1.0.8 - insomnia-plugin-core-themes@1.0.5 - insomnia-plugin-default-headers@1.1.9 - insomnia-plugin-file@1.0.7 - insomnia-plugin-hash@1.0.7 - insomnia-plugin-jsonpath@1.0.12 - insomnia-plugin-now@1.0.11 - insomnia-plugin-os@1.0.13 - insomnia-plugin-prompt@1.1.9 - insomnia-plugin-request@1.0.18 - insomnia-plugin-response@1.0.16 - insomnia-plugin-uuid@1.0.10 * Broken but w/e * Some tweaks * Big refactor. Create local snapshots and push done * POC merging and a lot of improvements * Lots of work done on initial UI/UX * Fix old tests * Atomic writes and size-based batches * Update StageEntry definition once again to be better * Factor out GraphQL query logic * Merge algorithm, history modal, other minor things * Fix test * Merge, checkout, revert w/ user changes now work * Force UI to refresh when switching branches changes active request * Rough draft pull() and some cleanup * E2EE stuff and some refactoring * Add ability to share project with team and fixed tests * VCS now created in root component and better remote project handling * Remove unused definition * Publish - insomnia-account@0.0.2 - insomnia-app@1.1.1 - insomnia-cookies@0.0.14 - insomnia-httpsnippet@1.16.20 - insomnia-importers@2.0.15 - insomnia-libcurl@0.0.25 - insomnia-prettify@0.1.9 - insomnia-sync@0.0.2 - insomnia-url@0.1.8 - insomnia-xpath@1.0.11 - insomnia-plugin-base64@1.0.8 - insomnia-plugin-cookie-jar@1.0.10 - insomnia-plugin-core-themes@1.0.7 - insomnia-plugin-file@1.0.9 - insomnia-plugin-hash@1.0.9 - insomnia-plugin-jsonpath@1.0.14 - insomnia-plugin-now@1.0.13 - insomnia-plugin-os@1.0.15 - insomnia-plugin-prompt@1.1.11 - insomnia-plugin-request@1.0.20 - insomnia-plugin-response@1.0.18 - insomnia-plugin-uuid@1.0.12 * Move some deps around * Fix Flow errors * Update package.json * Fix eslint errors * Fix tests * Update deps * bootstrap insomnia-sync * TRy fixing appveyor * Try something else * Bump lerna * try powershell * Try again * Fix imports * Fixed errors * sync types refactor * Show remote projects in workspace dropdown * Improved pulling of non-local workspaces * Loading indicators and some tweaks * Clean up sync staging modal * Some sync improvements: - No longer store stage - Upgrade Electron - Sync UI/UX improvements * Fix snyc tests * Upgraded deps and hot loader tweaks (it's broken for some reason) * Fix tests * Branches dialog, network refactoring, some tweaks * Fixed merging when other branch is empty * A bunch of small fixes from real testing * Fixed pull merge logic * Fix tests * Some bug fixes * A few small tweaks * Conflict resolution and other improvements * Fix tests * Add revert changes * Deal with duplicate projects per workspace * Some tweaks and accessibility improvements * Tooltip accessibility * Fix API endpoint * Fix tests * Remove jest dep from insomnia-importers
2019-04-18 00:50:03 +00:00
export async function all(): Promise<Array<Response>> {
return db.all(type);
}
export async function removeForRequest(parentId: string, environmentId?: string | null) {
const settings = await models.settings.getOrCreate();
const query: Object = {
parentId,
};
// Only add if not undefined. null is not the same as undefined
// null: find responses sent from base environment
// undefined: find all responses
if (environmentId !== undefined && settings.filterResponsesByEnv) {
query.environmentId = environmentId;
}
// Also delete legacy responses here or else the user will be confused as to
// why some responses are still showing in the UI.
await db.removeWhere(type, query);
}
2018-06-25 17:42:50 +00:00
export function remove(response: Response) {
2017-07-19 04:48:28 +00:00
return db.remove(response);
2017-06-12 21:48:17 +00:00
}
async function _findRecentForRequest(
requestId: string,
environmentId: string | null,
limit: number,
): Promise<Array<Response>> {
2020-02-11 19:24:52 +00:00
const query: Object = {
parentId: requestId,
};
// Filter responses by environment if setting is enabled
if ((await models.settings.getOrCreate()).filterResponsesByEnv) {
query.environmentId = environmentId;
}
return db.findMostRecentlyModified(type, query, limit);
}
export async function getLatestForRequest(
requestId: string,
environmentId: string | null,
): Promise<Response | null> {
const responses = await _findRecentForRequest(requestId, environmentId, 1);
const response = (responses[0]: ?Response);
return response || null;
}
Version Control (beta) (#1439) * VCS proof of concept underway! * Stuff * Some things * Replace deprecated Electron makeSingleInstance * Rename `window` variables so not to be confused with window object * Don't unnecessarily update request when URL does not change * Regenerate package-lock * Fix tests + ESLint * Publish - insomnia-app@1.0.49 - insomnia-cookies@0.0.12 - insomnia-httpsnippet@1.16.18 - insomnia-importers@2.0.13 - insomnia-libcurl@0.0.23 - insomnia-prettify@0.1.7 - insomnia-url@0.1.6 - insomnia-xpath@1.0.9 - insomnia-plugin-base64@1.0.6 - insomnia-plugin-cookie-jar@1.0.8 - insomnia-plugin-core-themes@1.0.5 - insomnia-plugin-default-headers@1.1.9 - insomnia-plugin-file@1.0.7 - insomnia-plugin-hash@1.0.7 - insomnia-plugin-jsonpath@1.0.12 - insomnia-plugin-now@1.0.11 - insomnia-plugin-os@1.0.13 - insomnia-plugin-prompt@1.1.9 - insomnia-plugin-request@1.0.18 - insomnia-plugin-response@1.0.16 - insomnia-plugin-uuid@1.0.10 * Broken but w/e * Some tweaks * Big refactor. Create local snapshots and push done * POC merging and a lot of improvements * Lots of work done on initial UI/UX * Fix old tests * Atomic writes and size-based batches * Update StageEntry definition once again to be better * Factor out GraphQL query logic * Merge algorithm, history modal, other minor things * Fix test * Merge, checkout, revert w/ user changes now work * Force UI to refresh when switching branches changes active request * Rough draft pull() and some cleanup * E2EE stuff and some refactoring * Add ability to share project with team and fixed tests * VCS now created in root component and better remote project handling * Remove unused definition * Publish - insomnia-account@0.0.2 - insomnia-app@1.1.1 - insomnia-cookies@0.0.14 - insomnia-httpsnippet@1.16.20 - insomnia-importers@2.0.15 - insomnia-libcurl@0.0.25 - insomnia-prettify@0.1.9 - insomnia-sync@0.0.2 - insomnia-url@0.1.8 - insomnia-xpath@1.0.11 - insomnia-plugin-base64@1.0.8 - insomnia-plugin-cookie-jar@1.0.10 - insomnia-plugin-core-themes@1.0.7 - insomnia-plugin-file@1.0.9 - insomnia-plugin-hash@1.0.9 - insomnia-plugin-jsonpath@1.0.14 - insomnia-plugin-now@1.0.13 - insomnia-plugin-os@1.0.15 - insomnia-plugin-prompt@1.1.11 - insomnia-plugin-request@1.0.20 - insomnia-plugin-response@1.0.18 - insomnia-plugin-uuid@1.0.12 * Move some deps around * Fix Flow errors * Update package.json * Fix eslint errors * Fix tests * Update deps * bootstrap insomnia-sync * TRy fixing appveyor * Try something else * Bump lerna * try powershell * Try again * Fix imports * Fixed errors * sync types refactor * Show remote projects in workspace dropdown * Improved pulling of non-local workspaces * Loading indicators and some tweaks * Clean up sync staging modal * Some sync improvements: - No longer store stage - Upgrade Electron - Sync UI/UX improvements * Fix snyc tests * Upgraded deps and hot loader tweaks (it's broken for some reason) * Fix tests * Branches dialog, network refactoring, some tweaks * Fixed merging when other branch is empty * A bunch of small fixes from real testing * Fixed pull merge logic * Fix tests * Some bug fixes * A few small tweaks * Conflict resolution and other improvements * Fix tests * Add revert changes * Deal with duplicate projects per workspace * Some tweaks and accessibility improvements * Tooltip accessibility * Fix API endpoint * Fix tests * Remove jest dep from insomnia-importers
2019-04-18 00:50:03 +00:00
export async function create(patch: Object = {}, maxResponses: number = 20) {
2016-09-21 20:32:45 +00:00
if (!patch.parentId) {
throw new Error('New Response missing `parentId`');
}
2018-06-25 17:42:50 +00:00
const { parentId } = patch;
// Create request version snapshot
const request = await models.request.getById(parentId);
2018-10-17 16:42:33 +00:00
const requestVersion = request ? await models.requestVersion.create(request) : null;
patch.requestVersionId = requestVersion ? requestVersion._id : null;
// Filter responses by environment if setting is enabled
2020-02-11 19:24:52 +00:00
const query: Object = { parentId };
if (
(await models.settings.getOrCreate()).filterResponsesByEnv &&
patch.hasOwnProperty('environmentId')
) {
query.environmentId = patch.environmentId;
}
// Delete all other responses before creating the new one
const allResponses = await db.findMostRecentlyModified(type, query, Math.max(1, maxResponses));
const recentIds = allResponses.map(r => r._id);
// Remove all that were in the last query, except the first `maxResponses` IDs
await db.removeWhere(type, { ...query, _id: { $nin: recentIds } });
// Actually create the new response
return db.docCreate(type, patch);
}
2016-09-21 20:32:45 +00:00
2018-06-25 17:42:50 +00:00
export function getLatestByParentId(parentId: string) {
return db.getMostRecentlyModified(type, { parentId });
}
2018-10-17 16:42:33 +00:00
export function getBodyStream<T>(response: Object, readFailureValue: ?T): Readable | null | T {
return getBodyStreamFromPath(response.bodyPath || '', response.bodyCompression, readFailureValue);
}
2018-10-17 16:42:33 +00:00
export function getBodyBuffer<T>(response: Object, readFailureValue: ?T): Buffer | T | null {
return getBodyBufferFromPath(response.bodyPath || '', response.bodyCompression, readFailureValue);
}
export function getTimeline(response: Object): Array<ResponseTimelineEntry> {
return getTimelineFromPath(response.timelinePath || '');
}
2018-06-25 17:42:50 +00:00
function getBodyStreamFromPath<T>(
bodyPath: string,
compression: string | null,
readFailureValue: ?T,
): Readable | null | T {
// No body, so return empty Buffer
if (!bodyPath) {
return null;
}
try {
fs.statSync(bodyPath);
} catch (err) {
console.warn('Failed to read response body', err.message);
return readFailureValue === undefined ? null : readFailureValue;
}
const readStream = fs.createReadStream(bodyPath);
if (compression === 'zip') {
return readStream.pipe(zlib.createGunzip());
} else {
return readStream;
}
}
2018-06-25 17:42:50 +00:00
function getBodyBufferFromPath<T>(
bodyPath: string,
compression: string | null,
readFailureValue: ?T,
): Buffer | T | null {
// No body, so return empty Buffer
if (!bodyPath) {
return Buffer.alloc(0);
}
try {
const rawBuffer = fs.readFileSync(bodyPath);
if (compression === 'zip') {
return zlib.gunzipSync(rawBuffer);
} else {
return rawBuffer;
}
} catch (err) {
console.warn('Failed to read response body', err.message);
return readFailureValue === undefined ? null : readFailureValue;
}
}
function getTimelineFromPath(timelinePath: string): Array<ResponseTimelineEntry> {
// 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 [];
}
}
2018-06-25 17:42:50 +00:00
async function migrateBodyToFileSystem(doc: Object) {
if (doc.hasOwnProperty('body') && doc._id && !doc.bodyPath) {
const bodyBuffer = Buffer.from(doc.body, doc.encoding || 'utf8');
const dir = path.join(getDataDirectory(), 'responses');
mkdirp.sync(dir);
2018-06-25 17:42:50 +00:00
const hash = crypto
.createHash('md5')
.update(bodyBuffer || '')
.digest('hex');
const bodyPath = path.join(dir, `${hash}.zip`);
try {
const buff = bodyBuffer || Buffer.from('');
fs.writeFileSync(bodyPath, buff);
} catch (err) {
console.warn('Failed to write response body to file', err.message);
}
2018-06-25 17:42:50 +00:00
return db.docUpdate(doc, { bodyPath, bodyCompression: null });
} else {
return doc;
}
}
2018-06-25 17:42:50 +00:00
function migrateBodyCompression(doc: Object) {
2017-11-28 11:25:05 +00:00
if (doc.bodyCompression === '__NEEDS_MIGRATION__') {
doc.bodyCompression = 'zip';
}
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);
const files = fs.readdirSync(responsesDir);
if (files.length === 0) {
return;
}
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 (const filePath of files) {
if (whitelistFiles.indexOf(filePath) >= 0) {
continue;
}
try {
fs.unlinkSync(path.join(responsesDir, filePath));
} catch (err) {
// Just keep going, doesn't matter
}
}
}