2017-07-18 22:10:57 +00:00
|
|
|
// @flow
|
2017-07-19 04:48:28 +00:00
|
|
|
import type {BaseModel} from './index';
|
|
|
|
import * as models from './index';
|
|
|
|
|
2017-06-30 03:30:22 +00:00
|
|
|
import fs from 'fs';
|
|
|
|
import crypto from 'crypto';
|
|
|
|
import path from 'path';
|
|
|
|
import mkdirp from 'mkdirp';
|
|
|
|
import * as electron from 'electron';
|
2016-11-27 21:42:38 +00:00
|
|
|
import {MAX_RESPONSES} from '../common/constants';
|
2017-06-30 03:30:22 +00:00
|
|
|
import * as db from '../common/database';
|
|
|
|
import {compress, decompress} from '../common/misc';
|
2016-09-21 20:32:45 +00:00
|
|
|
|
2016-11-22 19:42:10 +00:00
|
|
|
export const name = 'Response';
|
2016-10-02 20:57:00 +00:00
|
|
|
export const type = 'Response';
|
|
|
|
export const prefix = 'res';
|
2017-03-23 22:10:42 +00:00
|
|
|
export const canDuplicate = false;
|
2016-11-22 19:42:10 +00:00
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export type ResponseHeader = {
|
|
|
|
name: string,
|
|
|
|
value: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ResponseTimelineEntry = {
|
|
|
|
name: string,
|
|
|
|
value: string
|
|
|
|
}
|
|
|
|
|
2017-07-19 04:48:28 +00:00
|
|
|
type BaseResponse = {
|
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,
|
2017-08-11 21:44:34 +00:00
|
|
|
bytesContent: number,
|
2017-07-18 22:10:57 +00:00
|
|
|
elapsedTime: number,
|
|
|
|
headers: Array<ResponseHeader>,
|
|
|
|
timeline: Array<ResponseTimelineEntry>,
|
|
|
|
bodyPath: string, // Actual bodies are stored on the filesystem
|
|
|
|
error: string,
|
|
|
|
requestVersionId: string | null,
|
|
|
|
|
|
|
|
// Things from the request
|
|
|
|
settingStoreCookies: boolean | null,
|
|
|
|
settingSendCookies: boolean | null
|
|
|
|
};
|
|
|
|
|
2017-07-19 04:48:28 +00:00
|
|
|
export type Response = BaseModel & BaseResponse;
|
|
|
|
|
|
|
|
export function init (): BaseResponse {
|
2016-11-10 01:15:27 +00:00
|
|
|
return {
|
2016-10-02 20:57:00 +00:00
|
|
|
statusCode: 0,
|
|
|
|
statusMessage: '',
|
2017-11-13 23:10:53 +00:00
|
|
|
httpVersion: '',
|
2016-10-27 16:45:44 +00:00
|
|
|
contentType: '',
|
2016-10-02 20:57:00 +00:00
|
|
|
url: '',
|
|
|
|
bytesRead: 0,
|
2017-08-11 21:44:34 +00:00
|
|
|
bytesContent: -1, // -1 means that it was legacy and this property didn't exist yet
|
2016-10-02 20:57:00 +00:00
|
|
|
elapsedTime: 0,
|
|
|
|
headers: [],
|
2017-03-28 22:45:23 +00:00
|
|
|
timeline: [],
|
2017-06-30 03:30:22 +00:00
|
|
|
bodyPath: '', // Actual bodies are stored on the filesystem
|
2017-03-29 23:09:28 +00:00
|
|
|
error: '',
|
2017-06-12 21:49:46 +00:00
|
|
|
requestVersionId: null,
|
2017-03-29 23:09:28 +00:00
|
|
|
|
|
|
|
// Things from the request
|
|
|
|
settingStoreCookies: null,
|
|
|
|
settingSendCookies: null
|
2017-03-03 20:09:08 +00:00
|
|
|
};
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-21 20:32:45 +00:00
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export function migrate (doc: Object) {
|
2017-06-30 03:30:22 +00:00
|
|
|
doc = migrateBody(doc);
|
2016-11-22 19:42:10 +00:00
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export function getById (id: string) {
|
2016-11-27 21:42:38 +00:00
|
|
|
return db.get(type, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function all () {
|
|
|
|
return db.all(type);
|
|
|
|
}
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export async function removeForRequest (parentId: string) {
|
2017-07-17 18:20:38 +00:00
|
|
|
await db.removeWhere(type, {parentId});
|
2016-11-27 21:42:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-19 04:48:28 +00:00
|
|
|
export function remove (response: Response) {
|
|
|
|
return db.remove(response);
|
2017-06-12 21:48:17 +00:00
|
|
|
}
|
|
|
|
|
2017-11-01 11:23:22 +00:00
|
|
|
export async function findRecentForRequest (requestId: string, limit: number): Promise<Array<Response>> {
|
|
|
|
const responses = await db.findMostRecentlyModified(type, {parentId: requestId}, limit);
|
|
|
|
return responses;
|
2016-11-27 21:42:38 +00:00
|
|
|
}
|
|
|
|
|
2017-11-01 11:23:22 +00:00
|
|
|
export async function getLatestForRequest (requestId: string): Promise<Response | null> {
|
2017-02-20 18:32:27 +00:00
|
|
|
const responses = await findRecentForRequest(requestId, 1);
|
2017-11-01 11:23:22 +00:00
|
|
|
const response = (responses[0]: ?Response);
|
|
|
|
return response || null;
|
2017-02-20 18:32:27 +00:00
|
|
|
}
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export async function create (patch: Object = {}, bodyBuffer: Buffer | null = null) {
|
2016-09-21 20:32:45 +00:00
|
|
|
if (!patch.parentId) {
|
|
|
|
throw new Error('New Response missing `parentId`');
|
|
|
|
}
|
|
|
|
|
2016-11-27 21:42:38 +00:00
|
|
|
const {parentId} = patch;
|
|
|
|
|
2017-06-12 21:49:46 +00:00
|
|
|
// Create request version snapshot
|
|
|
|
const request = await models.request.getById(parentId);
|
|
|
|
const requestVersion = request ? await models.requestVersion.create(request) : null;
|
|
|
|
patch.requestVersionId = requestVersion ? requestVersion._id : null;
|
|
|
|
|
2016-11-27 21:42:38 +00:00
|
|
|
// Delete all other responses before creating the new one
|
|
|
|
const allResponses = await db.findMostRecentlyModified(type, {parentId}, MAX_RESPONSES);
|
|
|
|
const recentIds = allResponses.map(r => r._id);
|
2017-07-17 18:20:38 +00:00
|
|
|
await db.removeWhere(type, {parentId, _id: {$nin: recentIds}});
|
2016-11-27 21:42:38 +00:00
|
|
|
|
|
|
|
// Actually create the new response
|
2017-06-30 03:30:22 +00:00
|
|
|
const bodyPath = bodyBuffer ? storeBodyBuffer(bodyBuffer) : '';
|
|
|
|
return db.docCreate(type, {bodyPath}, patch);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-21 20:32:45 +00:00
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export function getLatestByParentId (parentId: string) {
|
2016-10-02 20:57:00 +00:00
|
|
|
return db.getMostRecentlyModified(type, {parentId});
|
|
|
|
}
|
2017-06-30 03:30:22 +00:00
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export function getBodyBuffer (response: Response, readFailureValue: any = null) {
|
2017-06-30 03:30:22 +00:00
|
|
|
// No body, so return empty Buffer
|
|
|
|
if (!response.bodyPath) {
|
2017-11-20 16:07:36 +00:00
|
|
|
return Buffer.alloc(0);
|
2017-06-30 03:30:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return decompress(fs.readFileSync(response.bodyPath));
|
|
|
|
} catch (err) {
|
|
|
|
console.warn('Failed to read response body', err.message);
|
|
|
|
return readFailureValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
export function storeBodyBuffer (bodyBuffer: Buffer | null) {
|
2017-06-30 03:30:22 +00:00
|
|
|
const root = electron.remote.app.getPath('userData');
|
|
|
|
const dir = path.join(root, 'responses');
|
|
|
|
|
|
|
|
mkdirp.sync(dir);
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
const hash = crypto.createHash('md5').update(bodyBuffer || '').digest('hex');
|
2017-06-30 03:30:22 +00:00
|
|
|
const fullPath = path.join(dir, `${hash}.zip`);
|
|
|
|
|
|
|
|
try {
|
2017-07-19 02:54:03 +00:00
|
|
|
fs.writeFileSync(fullPath, compress(bodyBuffer || Buffer.from('')));
|
2017-06-30 03:30:22 +00:00
|
|
|
} catch (err) {
|
|
|
|
console.warn('Failed to write response body to file', err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
return fullPath;
|
|
|
|
}
|
|
|
|
|
2017-07-18 22:10:57 +00:00
|
|
|
function migrateBody (doc: Object) {
|
2017-06-30 03:30:22 +00:00
|
|
|
if (doc.hasOwnProperty('body') && doc._id && !doc.bodyPath) {
|
|
|
|
const bodyBuffer = Buffer.from(doc.body, doc.encoding || 'utf8');
|
|
|
|
const bodyPath = storeBodyBuffer(bodyBuffer);
|
|
|
|
const newDoc = Object.assign(doc, {bodyPath});
|
|
|
|
db.docUpdate(newDoc);
|
|
|
|
return newDoc;
|
|
|
|
} else {
|
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
}
|