mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
More Plugin Hooks (#352)
* Playing around with pre-request hooks * Added response hooks * More flow types * Flow types on wrapper.js * Flow types on plugin folder * Basic tests for plugin hooks * Make DB initilize for all tests no matter what * Touch
This commit is contained in:
parent
0902aa6ef9
commit
09c219fb6d
7
app/__jest__/before-each.js
Normal file
7
app/__jest__/before-each.js
Normal file
@ -0,0 +1,7 @@
|
||||
import * as db from '../common/database';
|
||||
import * as models from '../models';
|
||||
|
||||
export default function () {
|
||||
// Setup the local database in case it's used
|
||||
db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import 'whatwg-fetch';
|
||||
import beforeEach from './before-each';
|
||||
|
||||
const localStorageMock = (function () {
|
||||
let store = {};
|
||||
@ -18,3 +19,4 @@ const localStorageMock = (function () {
|
||||
|
||||
global.localStorage = localStorageMock;
|
||||
global.require = require;
|
||||
global.insomniaBeforeEach = beforeEach;
|
||||
|
@ -2,6 +2,7 @@ import * as appPackage from '../package.json';
|
||||
import * as globalPackage from '../../package.json';
|
||||
|
||||
describe('package.json', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('all app dependencies should be same in global', () => {
|
||||
for (const name of Object.keys(appPackage.dependencies)) {
|
||||
const expected = globalPackage.dependencies[name];
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as renderer from '../renderer';
|
||||
|
||||
describe('imports', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('ui module should import successfully', () => {
|
||||
expect(renderer).toBeDefined();
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as analytics from '../index';
|
||||
import {GA_HOST, getAppVersion, getAppPlatform} from '../../common/constants';
|
||||
import * as db from '../../common/database';
|
||||
import {GA_HOST, getAppPlatform, getAppVersion} from '../../common/constants';
|
||||
import * as models from '../../models';
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
window.localStorage = {};
|
||||
global.document = {
|
||||
getElementsByTagName () {
|
||||
@ -16,7 +16,6 @@ describe('init()', () => {
|
||||
};
|
||||
}
|
||||
};
|
||||
return db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -27,10 +27,12 @@ export async function init (accountId) {
|
||||
if (window) {
|
||||
window.addEventListener('error', e => {
|
||||
trackEvent('Error', 'Uncaught Error');
|
||||
console.error('Uncaught Error', e);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
trackEvent('Error', 'Uncaught Promise');
|
||||
console.error('Unhandled Promise', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {FLEXIBLE_URL_REGEX} from '../constants';
|
||||
describe('URL Regex', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('matches valid URLs', () => {
|
||||
expect('https://google.com').toMatch(FLEXIBLE_URL_REGEX);
|
||||
expect('http://google.com').toMatch(FLEXIBLE_URL_REGEX);
|
||||
|
@ -2,6 +2,7 @@ import {CookieJar} from 'tough-cookie';
|
||||
import * as cookieUtils from '../cookies';
|
||||
|
||||
describe('jarFromCookies()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('returns valid cookies', done => {
|
||||
const jar = cookieUtils.jarFromCookies([{
|
||||
key: 'foo',
|
||||
@ -27,6 +28,7 @@ describe('jarFromCookies()', () => {
|
||||
});
|
||||
|
||||
describe('cookiesFromJar()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('returns valid jar', async () => {
|
||||
const d = new Date();
|
||||
const initialCookies = [{
|
||||
@ -64,6 +66,7 @@ describe('cookiesFromJar()', () => {
|
||||
});
|
||||
|
||||
describe('cookieHeaderValueForUri()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('gets cookies for valid case', async () => {
|
||||
const jar = cookieUtils.jarFromCookies([{
|
||||
key: 'foo',
|
||||
@ -97,6 +100,7 @@ describe('cookieHeaderValueForUri()', () => {
|
||||
});
|
||||
|
||||
describe('cookieToString()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('does it\'s thing', async () => {
|
||||
const jar = cookieUtils.jarFromCookies([{
|
||||
key: 'foo',
|
||||
|
@ -14,6 +14,7 @@ function loadFixture (name) {
|
||||
}
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('handles being initialized twice', async () => {
|
||||
await db.init(models.types(), {inMemoryOnly: true});
|
||||
await db.init(models.types(), {inMemoryOnly: true});
|
||||
@ -22,6 +23,7 @@ describe('init()', () => {
|
||||
});
|
||||
|
||||
describe('onChange()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('handles change listeners', async () => {
|
||||
const doc = {
|
||||
type: models.request.type,
|
||||
@ -51,6 +53,7 @@ describe('onChange()', () => {
|
||||
});
|
||||
|
||||
describe('bufferChanges()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('properly buffers changes', async () => {
|
||||
const doc = {
|
||||
type: models.request.type,
|
||||
@ -88,8 +91,7 @@ describe('bufferChanges()', () => {
|
||||
});
|
||||
|
||||
describe('requestCreate()', () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('creates a valid request', async () => {
|
||||
const now = Date.now();
|
||||
|
||||
@ -124,7 +126,7 @@ describe('requestCreate()', () => {
|
||||
|
||||
describe('requestGroupDuplicate()', () => {
|
||||
beforeEach(async () => {
|
||||
await db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
await global.insomniaBeforeEach();
|
||||
await loadFixture('nestedfolders');
|
||||
});
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import * as harUtils from '../har';
|
||||
import * as db from '../database';
|
||||
import * as render from '../render';
|
||||
import * as models from '../../models';
|
||||
import {AUTH_BASIC} from '../constants';
|
||||
|
||||
describe('exportHarWithRequest()', () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders does it correctly', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const cookies = [{
|
||||
|
@ -1,13 +1,9 @@
|
||||
import * as db from '../database';
|
||||
import * as models from '../../models';
|
||||
import * as importUtil from '../import';
|
||||
import {getAppVersion} from '../constants';
|
||||
|
||||
describe('export()', () => {
|
||||
beforeEach(async () => {
|
||||
await db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
});
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('succeed with username and password', async () => {
|
||||
const w = await models.workspace.create({name: 'Workspace'});
|
||||
const r1 = await models.request.create({name: 'Request', parentId: w._id});
|
||||
|
@ -3,7 +3,8 @@ import fs from 'fs';
|
||||
import LocalStorage from '../local-storage';
|
||||
|
||||
describe('LocalStorage()', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
jest.useFakeTimers();
|
||||
|
||||
// There has to be a better way to reset this...
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as misc from '../misc';
|
||||
|
||||
describe('getBasicAuthHeader()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('succeed with username and password', () => {
|
||||
const header = misc.getBasicAuthHeader('user', 'password');
|
||||
expect(header).toEqual({
|
||||
@ -35,6 +36,7 @@ describe('getBasicAuthHeader()', () => {
|
||||
});
|
||||
|
||||
describe('hasAuthHeader()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('finds valid header', () => {
|
||||
const yes = misc.hasAuthHeader([
|
||||
{name: 'foo', value: 'bar'},
|
||||
@ -55,6 +57,7 @@ describe('hasAuthHeader()', () => {
|
||||
});
|
||||
|
||||
describe('generateId()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('generates a valid ID', () => {
|
||||
const id = misc.generateId('foo');
|
||||
expect(id).toMatch(/^foo_[a-z0-9]{32}$/);
|
||||
@ -67,6 +70,7 @@ describe('generateId()', () => {
|
||||
});
|
||||
|
||||
describe('setDefaultProtocol()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('no-ops on empty url', () => {
|
||||
const url = misc.setDefaultProtocol('');
|
||||
expect(url).toBe('');
|
||||
@ -94,6 +98,7 @@ describe('setDefaultProtocol()', () => {
|
||||
});
|
||||
|
||||
describe('prepareUrlForSending()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('does not touch normal url', () => {
|
||||
const url = misc.prepareUrlForSending('http://google.com');
|
||||
expect(url).toBe('http://google.com/');
|
||||
@ -151,6 +156,7 @@ describe('prepareUrlForSending()', () => {
|
||||
});
|
||||
|
||||
describe('filterHeaders()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('handles bad headers', () => {
|
||||
expect(misc.filterHeaders(null, null)).toEqual([]);
|
||||
expect(misc.filterHeaders([], null)).toEqual([]);
|
||||
@ -164,7 +170,8 @@ describe('filterHeaders()', () => {
|
||||
});
|
||||
|
||||
describe('keyedDebounce()', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
jest.useFakeTimers();
|
||||
|
||||
// There has to be a better way to reset this...
|
||||
@ -199,7 +206,8 @@ describe('keyedDebounce()', () => {
|
||||
});
|
||||
|
||||
describe('debounce()', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
jest.useFakeTimers();
|
||||
|
||||
// There has to be a better way to reset this...
|
||||
|
@ -3,6 +3,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('prettify()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
const basePath = path.join(__dirname, '../__fixtures__/prettify');
|
||||
const files = fs.readdirSync(basePath);
|
||||
for (const file of files) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as querystringUtils from '../querystring';
|
||||
|
||||
describe('getBasicAuthHeader()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('gets joiner for bare URL', () => {
|
||||
const joiner = querystringUtils.getJoiner('http://google.com');
|
||||
expect(joiner).toBe('?');
|
||||
@ -35,6 +36,7 @@ describe('getBasicAuthHeader()', () => {
|
||||
});
|
||||
|
||||
describe('joinUrl()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('joins bare URL', () => {
|
||||
const url = querystringUtils.joinUrl(
|
||||
'http://google.com',
|
||||
@ -77,6 +79,7 @@ describe('joinUrl()', () => {
|
||||
});
|
||||
|
||||
describe('build()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('builds simple param', () => {
|
||||
const str = querystringUtils.build({name: 'foo', value: 'bar??'});
|
||||
expect(str).toBe('foo=bar%3F%3F');
|
||||
@ -102,6 +105,7 @@ describe('build()', () => {
|
||||
});
|
||||
|
||||
describe('buildFromParams()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('builds from params', () => {
|
||||
const str = querystringUtils.buildFromParams([
|
||||
{name: 'foo', value: 'bar??'},
|
||||
@ -127,6 +131,7 @@ describe('buildFromParams()', () => {
|
||||
});
|
||||
|
||||
describe('deconstructToParams()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('builds from params', () => {
|
||||
const str = querystringUtils.deconstructToParams(
|
||||
'foo=bar%3F%3F&hello&hi%20there=bar%3F%3F&=&=val'
|
||||
@ -152,6 +157,7 @@ describe('deconstructToParams()', () => {
|
||||
});
|
||||
|
||||
describe('deconstructToParams()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('builds from params not strict', () => {
|
||||
const str = querystringUtils.deconstructToParams(
|
||||
'foo=bar%3F%3F&hello&hi%20there=bar%3F%3F&=&=val',
|
||||
|
@ -4,6 +4,7 @@ import * as models from '../../models';
|
||||
jest.mock('electron');
|
||||
|
||||
describe('render()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders hello world', async () => {
|
||||
const rendered = await renderUtils.render('Hello {{ msg }}!', {msg: 'World'});
|
||||
expect(rendered).toBe('Hello World!');
|
||||
@ -30,6 +31,7 @@ describe('render()', () => {
|
||||
});
|
||||
|
||||
describe('buildRenderContext()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('cascades properly', async () => {
|
||||
const ancestors = [
|
||||
{
|
||||
@ -270,6 +272,7 @@ describe('buildRenderContext()', () => {
|
||||
});
|
||||
|
||||
describe('render()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('correctly renders simple Object', async () => {
|
||||
const newObj = await renderUtils.render({
|
||||
foo: '{{ foo }}',
|
||||
|
@ -53,7 +53,7 @@ export const GA_ID = 'UA-86416787-1';
|
||||
export const GA_HOST = 'desktop.insomnia.rest';
|
||||
export const CHANGELOG_URL = process.env.INSOMNIA_SYNC_URL || 'https://changelog.insomnia.rest/changelog.json';
|
||||
export const CHANGELOG_PAGE = 'https://insomnia.rest/changelog/';
|
||||
export const STATUS_CODE_RENDER_FAILED = -333;
|
||||
export const STATUS_CODE_PLUGIN_ERROR = -222;
|
||||
export const LARGE_RESPONSE_MB = 5;
|
||||
export const FLEXIBLE_URL_REGEX = /^(http|https):\/\/[0-9a-zA-Z\-_.]+[/\w.\-+=:\][@%^*&!#?;]*/;
|
||||
export const CHECK_FOR_UPDATES_INTERVAL = 1000 * 60 * 60 * 3; // 3 hours
|
||||
@ -253,6 +253,9 @@ export const RESPONSE_CODE_REASONS = {
|
||||
// Sourced from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
export const RESPONSE_CODE_DESCRIPTIONS = {
|
||||
|
||||
// Special
|
||||
[STATUS_CODE_PLUGIN_ERROR]: 'An Insomnia plugin threw an error which prevented the request from sending',
|
||||
|
||||
// 100s
|
||||
|
||||
100: 'This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.',
|
||||
|
@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import uuid from 'uuid';
|
||||
import zlib from 'zlib';
|
||||
import {join as pathJoin} from 'path';
|
||||
import {format as urlFormat, parse as urlParse} from 'url';
|
||||
import {DEBOUNCE_MILLIS, getAppVersion, isDevelopment} from './constants';
|
||||
import * as querystring from './querystring';
|
||||
@ -299,3 +300,11 @@ export function compress (inputBuffer: Buffer | string): Buffer {
|
||||
export function decompress (inputBuffer: Buffer | string): Buffer {
|
||||
return zlib.gunzipSync(inputBuffer);
|
||||
}
|
||||
|
||||
export function resolveHomePath (p: string): string {
|
||||
if (p.indexOf('~/') === 0) {
|
||||
return pathJoin(process.env.HOME || '/', p.slice(1));
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// @flow
|
||||
import type {Request} from '../models/request';
|
||||
import type {BaseModel} from '../models/index';
|
||||
|
||||
import clone from 'clone';
|
||||
import * as models from '../models';
|
||||
import {setDefaultProtocol} from './misc';
|
||||
@ -7,11 +11,27 @@ import * as templating from '../templating';
|
||||
export const KEEP_ON_ERROR = 'keep';
|
||||
export const THROW_ON_ERROR = 'throw';
|
||||
|
||||
export async function buildRenderContext (ancestors, rootEnvironment, subEnvironment, baseContext = {}) {
|
||||
if (!Array.isArray(ancestors)) {
|
||||
ancestors = [];
|
||||
}
|
||||
type Cookie = {
|
||||
domain: string,
|
||||
path: string,
|
||||
key: string,
|
||||
value: string,
|
||||
expires: number
|
||||
}
|
||||
|
||||
export type RenderedRequest = Request & {
|
||||
cookies: Array<{name: string, value: string, disabled?: boolean}>,
|
||||
cookieJar: {
|
||||
cookies: Array<Cookie>
|
||||
}
|
||||
};
|
||||
|
||||
export async function buildRenderContext (
|
||||
ancestors: Array<BaseModel> | null,
|
||||
rootEnvironment: {data: Object},
|
||||
subEnvironment: {data: Object},
|
||||
baseContext: Object = {}
|
||||
): Object {
|
||||
const environments = [];
|
||||
|
||||
if (rootEnvironment) {
|
||||
@ -22,11 +42,10 @@ export async function buildRenderContext (ancestors, rootEnvironment, subEnviron
|
||||
environments.push(subEnvironment.data);
|
||||
}
|
||||
|
||||
for (const doc of ancestors.reverse()) {
|
||||
if (!doc.environment) {
|
||||
continue;
|
||||
for (const doc of (ancestors || []).reverse()) {
|
||||
if (typeof doc.environment === 'object' && doc.environment !== null) {
|
||||
environments.push(doc.environment);
|
||||
}
|
||||
environments.push(doc.environment);
|
||||
}
|
||||
|
||||
// At this point, environments is a list of environments ordered
|
||||
@ -34,7 +53,7 @@ export async function buildRenderContext (ancestors, rootEnvironment, subEnviron
|
||||
// Do an Object.assign, but render each property as it overwrites. This
|
||||
// way we can keep same-name variables from the parent context.
|
||||
const renderContext = baseContext;
|
||||
for (const environment of environments) {
|
||||
for (const environment: Object of environments) {
|
||||
// Sort the keys that may have Nunjucks last, so that other keys get
|
||||
// defined first. Very important if env variables defined in same obj
|
||||
// (eg. {"foo": "{{ bar }}", "bar": "Hello World!"})
|
||||
@ -94,11 +113,17 @@ export async function buildRenderContext (ancestors, rootEnvironment, subEnviron
|
||||
* @param name - name to include in error message
|
||||
* @return {Promise.<*>}
|
||||
*/
|
||||
export async function render (obj, context = {}, blacklistPathRegex = null, errorMode = THROW_ON_ERROR, name = '') {
|
||||
export async function render<T> (
|
||||
obj: T,
|
||||
context: Object = {},
|
||||
blacklistPathRegex: RegExp | null = null,
|
||||
errorMode: string = THROW_ON_ERROR,
|
||||
name: string = ''
|
||||
): Promise<T> {
|
||||
// Make a deep copy so no one gets mad :)
|
||||
const newObj = clone(obj);
|
||||
|
||||
async function next (x, path = name) {
|
||||
async function next (x: any, path: string = name): Promise<any> {
|
||||
if (blacklistPathRegex && path.match(blacklistPathRegex)) {
|
||||
return x;
|
||||
}
|
||||
@ -116,7 +141,7 @@ export async function render (obj, context = {}, blacklistPathRegex = null, erro
|
||||
asStr === '[object Undefined]'
|
||||
) {
|
||||
// Do nothing to these types
|
||||
} else if (asStr === '[object String]') {
|
||||
} else if (typeof x === 'string') {
|
||||
try {
|
||||
x = await templating.render(x, {context, path});
|
||||
|
||||
@ -135,7 +160,7 @@ export async function render (obj, context = {}, blacklistPathRegex = null, erro
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
x[i] = await next(x[i], `${path}[${i}]`);
|
||||
}
|
||||
} else if (typeof x === 'object') {
|
||||
} else if (typeof x === 'object' && x !== null) {
|
||||
// Don't even try rendering disabled objects
|
||||
// Note, this logic probably shouldn't be here, but w/e for now
|
||||
if (x.disabled) {
|
||||
@ -155,7 +180,11 @@ export async function render (obj, context = {}, blacklistPathRegex = null, erro
|
||||
return next(newObj);
|
||||
}
|
||||
|
||||
export async function getRenderContext (request, environmentId, ancestors = null) {
|
||||
export async function getRenderContext (
|
||||
request: Request,
|
||||
environmentId: string,
|
||||
ancestors: Array<BaseModel> | null = null
|
||||
): Promise<Object> {
|
||||
if (!request) {
|
||||
return {};
|
||||
}
|
||||
@ -175,7 +204,7 @@ export async function getRenderContext (request, environmentId, ancestors = null
|
||||
const baseContext = {};
|
||||
baseContext.getMeta = () => ({
|
||||
requestId: request._id,
|
||||
workspaceId: workspace._id
|
||||
workspaceId: workspace ? workspace._id : 'n/a'
|
||||
});
|
||||
|
||||
// Generate the context we need to render
|
||||
@ -189,7 +218,10 @@ export async function getRenderContext (request, environmentId, ancestors = null
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function getRenderedRequest (request, environmentId) {
|
||||
export async function getRenderedRequest (
|
||||
request: Request,
|
||||
environmentId: string
|
||||
): Promise<RenderedRequest> {
|
||||
const ancestors = await db.withAncestors(request, [
|
||||
models.requestGroup.type,
|
||||
models.workspace.type
|
||||
@ -225,9 +257,31 @@ export async function getRenderedRequest (request, environmentId) {
|
||||
// Default the proto if it doesn't exist
|
||||
renderedRequest.url = setDefaultProtocol(renderedRequest.url);
|
||||
|
||||
// Add the yummy cookies
|
||||
// TODO: Don't deal with cookies in here
|
||||
renderedRequest.cookieJar = cookieJar;
|
||||
return {
|
||||
// Add the yummy cookies
|
||||
// TODO: Eventually get rid of RenderedRequest type and put these elsewhere
|
||||
cookieJar,
|
||||
cookies: [],
|
||||
|
||||
return renderedRequest;
|
||||
// NOTE: Flow doesn't like Object.assign, so we have to do each property manually
|
||||
// for now to convert Request to RenderedRequest.
|
||||
_id: renderedRequest._id,
|
||||
authentication: renderedRequest.authentication,
|
||||
body: renderedRequest.body,
|
||||
created: renderedRequest.created,
|
||||
modified: renderedRequest.modified,
|
||||
description: renderedRequest.description,
|
||||
headers: renderedRequest.headers,
|
||||
metaSortKey: renderedRequest.metaSortKey,
|
||||
method: renderedRequest.method,
|
||||
name: renderedRequest.name,
|
||||
parameters: renderedRequest.parameters,
|
||||
parentId: renderedRequest.parentId,
|
||||
settingDisableRenderRequestBody: renderedRequest.settingDisableRenderRequestBody,
|
||||
settingEncodeUrl: renderedRequest.settingEncodeUrl,
|
||||
settingSendCookies: renderedRequest.settingSendCookies,
|
||||
settingStoreCookies: renderedRequest.settingStoreCookies,
|
||||
type: renderedRequest.type,
|
||||
url: renderedRequest.url
|
||||
};
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ const v1UUIDs = [
|
||||
];
|
||||
|
||||
const v4UUIDs = [
|
||||
'cc1dd2ca-4275-747a-a881-99e8efd42403',
|
||||
'dd2ccc1a-2745-477a-881a-9e8ef9d42403',
|
||||
'e3e96e5f-dd68-4229-8b66-dee1f0940f3d',
|
||||
'a262d22b-5fa8-491c-9bd9-58fba03e301e',
|
||||
|
@ -1,12 +1,8 @@
|
||||
import * as db from '../../common/database';
|
||||
import * as requestModel from '../../models/request';
|
||||
import * as models from '../index';
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(() => {
|
||||
return db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
});
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('contains all required fields', async () => {
|
||||
Date.now = jest.fn().mockReturnValue(1478795580200);
|
||||
expect(requestModel.init()).toEqual({
|
||||
@ -28,16 +24,13 @@ describe('init()', () => {
|
||||
});
|
||||
|
||||
describe('create()', async () => {
|
||||
beforeEach(() => {
|
||||
return db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
});
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('creates a valid request', async () => {
|
||||
Date.now = jest.fn().mockReturnValue(1478795580200);
|
||||
|
||||
const request = await requestModel.create({name: 'Test Request', parentId: 'fld_124', description: 'A test Request'});
|
||||
const expected = {
|
||||
_id: 'req_dd2ccc1a2745477a881a9e8ef9d42403',
|
||||
_id: 'req_cc1dd2ca4275747aa88199e8efd42403',
|
||||
created: 1478795580200,
|
||||
modified: 1478795580200,
|
||||
parentId: 'fld_124',
|
||||
@ -68,10 +61,7 @@ describe('create()', async () => {
|
||||
});
|
||||
|
||||
describe('updateMimeType()', async () => {
|
||||
beforeEach(() => {
|
||||
return db.init(models.types(), {inMemoryOnly: true}, true);
|
||||
});
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('adds header when does not exist', async () => {
|
||||
const request = await requestModel.create({name: 'My Request', parentId: 'fld_1'});
|
||||
expect(request).not.toBeNull();
|
||||
@ -129,6 +119,7 @@ describe('updateMimeType()', async () => {
|
||||
});
|
||||
|
||||
describe('migrate()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('migrates basic case', () => {
|
||||
const original = {
|
||||
headers: [],
|
||||
|
@ -3,7 +3,8 @@ import * as electron from 'electron';
|
||||
import * as models from '../../models';
|
||||
|
||||
describe('migrate()', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
Date.now = jest.genMockFunction().mockReturnValue(1234567890);
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
@ -55,7 +55,6 @@ export function init (): BaseResponse {
|
||||
bytesRead: 0,
|
||||
elapsedTime: 0,
|
||||
headers: [],
|
||||
cookies: [],
|
||||
timeline: [],
|
||||
bodyPath: '', // Actual bodies are stored on the filesystem
|
||||
error: '',
|
||||
|
@ -19,7 +19,8 @@ type BaseSettings = {
|
||||
forceVerticalLayout: boolean,
|
||||
autoHideMenuBar: boolean,
|
||||
theme: string,
|
||||
disableAnalyticsTracking: boolean
|
||||
disableAnalyticsTracking: boolean,
|
||||
pluginPath: string
|
||||
};
|
||||
|
||||
export type Settings = BaseModel & Settings;
|
||||
@ -47,7 +48,8 @@ export function init (): BaseSettings {
|
||||
forceVerticalLayout: false,
|
||||
autoHideMenuBar: false,
|
||||
theme: 'default',
|
||||
disableAnalyticsTracking: false
|
||||
disableAnalyticsTracking: false,
|
||||
pluginPath: ''
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import certificateUrlParse from '../certificate-url-parse';
|
||||
import {parse as urlParse} from 'url';
|
||||
|
||||
describe('certificateUrlParse', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should return the result of url.parse if no wildcard paths are supplied', () => {
|
||||
const url = 'https://www.example.org:80/some/resources?query=1&other=2#myfragment';
|
||||
const expected = urlParse(url);
|
||||
|
@ -1,14 +1,12 @@
|
||||
import * as networkUtils from '../network';
|
||||
import * as db from '../../common/database';
|
||||
import {join as pathJoin, resolve as pathResolve} from 'path';
|
||||
import {getRenderedRequest} from '../../common/render';
|
||||
import * as models from '../../models';
|
||||
import {AUTH_BASIC, AUTH_AWS_IAM, CONTENT_TYPE_FILE, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, getAppVersion} from '../../common/constants';
|
||||
import {AUTH_AWS_IAM, AUTH_BASIC, CONTENT_TYPE_FILE, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, getAppVersion} from '../../common/constants';
|
||||
import {filterHeaders} from '../../common/misc';
|
||||
|
||||
describe('actuallySend()', () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('sends a generic request', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
@ -403,6 +401,7 @@ describe('actuallySend()', () => {
|
||||
});
|
||||
|
||||
describe('_getAwsAuthHeaders', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should generate expected headers', () => {
|
||||
const req = {
|
||||
authentication: {
|
||||
|
@ -1,7 +1,9 @@
|
||||
import urlMatchesCertHost from '../url-matches-cert-host';
|
||||
|
||||
describe('urlMatchesCertHost', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
describe('when the certificate host has no wildcard', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should return false if the requested host does not match the certificate host', () => {
|
||||
const requestUrl = 'https://www.example.org';
|
||||
const certificateHost = 'https://www.example.com';
|
||||
@ -52,6 +54,7 @@ describe('urlMatchesCertHost', () => {
|
||||
});
|
||||
|
||||
describe('when using wildcard certificate hosts', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should return true if the certificate host is only a wildcard', () => {
|
||||
const requestUrl = 'https://www.example.org/some/resources?query=1';
|
||||
const certificateHost = '*';
|
||||
@ -114,6 +117,7 @@ describe('urlMatchesCertHost', () => {
|
||||
});
|
||||
|
||||
describe('when an invalid certificate host is supplied', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should return false if the certificate host contains invalid characters', () => {
|
||||
const requestUrl = 'https://www.example.org/some/resources?query=1';
|
||||
const certificateHost = 'https://example!.org';
|
||||
|
@ -1,8 +1,9 @@
|
||||
// @flow
|
||||
import type {ResponseTimelineEntry} from '../models/response';
|
||||
import type {Request, RequestHeader} from '../models/request';
|
||||
import type {ResponseHeader, ResponseTimelineEntry} from '../models/response';
|
||||
import type {RequestHeader} from '../models/request';
|
||||
import type {Workspace} from '../models/workspace';
|
||||
import type {Settings} from '../models/settings';
|
||||
import type {RenderedRequest} from '../common/render';
|
||||
|
||||
import electron from 'electron';
|
||||
import mkdirp from 'mkdirp';
|
||||
@ -14,36 +15,34 @@ import {join as pathJoin} from 'path';
|
||||
import * as models from '../models';
|
||||
import * as querystring from '../common/querystring';
|
||||
import * as util from '../common/misc.js';
|
||||
import {AUTH_AWS_IAM, AUTH_BASIC, AUTH_DIGEST, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, getAppVersion} from '../common/constants';
|
||||
import {AUTH_AWS_IAM, AUTH_BASIC, AUTH_DIGEST, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, getAppVersion, STATUS_CODE_PLUGIN_ERROR} from '../common/constants';
|
||||
import {describeByteSize, hasAuthHeader, hasContentTypeHeader, hasUserAgentHeader, setDefaultProtocol} from '../common/misc';
|
||||
import {getRenderedRequest} from '../common/render';
|
||||
import fs from 'fs';
|
||||
import * as db from '../common/database';
|
||||
import * as CACerts from './cacert';
|
||||
import * as plugins from '../plugins/index';
|
||||
import * as pluginContexts from '../plugins/context/index';
|
||||
import {getAuthHeader} from './authentication';
|
||||
import {cookiesFromJar, jarFromCookies} from '../common/cookies';
|
||||
import urlMatchesCertHost from './url-matches-cert-host';
|
||||
import aws4 from 'aws4';
|
||||
|
||||
type Cookie = {
|
||||
domain: string,
|
||||
path: string,
|
||||
key: string,
|
||||
value: string,
|
||||
expires: number
|
||||
}
|
||||
|
||||
type CookieJar = {
|
||||
cookies: Array<Cookie>
|
||||
}
|
||||
|
||||
type RenderedRequest = Request & {
|
||||
cookies: Array<{name: string, value: string, disabled: boolean}>,
|
||||
cookieJar: CookieJar
|
||||
export type ResponsePatch = {
|
||||
statusMessage?: string,
|
||||
error?: string,
|
||||
url?: string,
|
||||
statusCode?: number,
|
||||
headers?: Array<ResponseHeader>,
|
||||
elapsedTime?: number,
|
||||
contentType?: string,
|
||||
bytesRead?: number,
|
||||
parentId?: string,
|
||||
settingStoreCookies?: boolean,
|
||||
settingSendCookies?: boolean,
|
||||
timeline?: Array<ResponseTimelineEntry>
|
||||
};
|
||||
|
||||
type ResponsePatch = {};
|
||||
|
||||
// Time since user's last keypress to wait before making the request
|
||||
const MAX_DELAY_TIME = 1000;
|
||||
|
||||
@ -69,14 +68,24 @@ export function _actuallySend (
|
||||
|
||||
/** Helper function to respond with a success */
|
||||
function respond (patch: ResponsePatch, bodyBuffer: ?Buffer = null): void {
|
||||
const response = Object.assign({
|
||||
const response = Object.assign(({
|
||||
parentId: renderedRequest._id,
|
||||
timeline: timeline,
|
||||
settingSendCookies: renderedRequest.settingSendCookies,
|
||||
settingStoreCookies: renderedRequest.settingStoreCookies
|
||||
}, patch);
|
||||
}: ResponsePatch), patch);
|
||||
|
||||
resolve({bodyBuffer, response});
|
||||
|
||||
// Apply plugin hooks and don't wait for them and don't throw from them
|
||||
process.nextTick(async () => {
|
||||
try {
|
||||
await _applyResponsePluginHooks(response, bodyBuffer);
|
||||
} catch (err) {
|
||||
// TODO: Better error handling here
|
||||
console.warn('Response plugin failed', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Helper function to respond with an error */
|
||||
@ -267,6 +276,10 @@ export function _actuallySend (
|
||||
].join('\t'));
|
||||
}
|
||||
|
||||
for (const {name, value} of renderedRequest.cookies) {
|
||||
setOpt(Curl.option.COOKIE, `${name}=${value}`);
|
||||
}
|
||||
|
||||
timeline.push({
|
||||
name: 'TEXT',
|
||||
value: 'Enable cookie sending with jar of ' +
|
||||
@ -421,7 +434,8 @@ export function _actuallySend (
|
||||
setOpt(Curl.option.PASSWORD, password || '');
|
||||
} else if (renderedRequest.authentication.type === AUTH_AWS_IAM) {
|
||||
if (!requestBody) {
|
||||
return handleError(new Error('AWS authentication not supported for provided body type'));
|
||||
return handleError(
|
||||
new Error('AWS authentication not supported for provided body type'));
|
||||
}
|
||||
const extraHeaders = _getAwsAuthHeaders(
|
||||
renderedRequest.authentication.accessKeyId || '',
|
||||
@ -533,7 +547,7 @@ export function _actuallySend (
|
||||
const bodyBuffer = Buffer.concat(dataBuffers, dataBuffersLength);
|
||||
|
||||
// Return the response data
|
||||
respond({
|
||||
const responsePatch = {
|
||||
headers,
|
||||
contentType,
|
||||
statusCode,
|
||||
@ -541,10 +555,12 @@ export function _actuallySend (
|
||||
elapsedTime: curl.getInfo(Curl.info.TOTAL_TIME) * 1000,
|
||||
bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD),
|
||||
url: curl.getInfo(Curl.info.EFFECTIVE_URL)
|
||||
}, bodyBuffer);
|
||||
};
|
||||
|
||||
// Close the request
|
||||
this.close();
|
||||
|
||||
respond(responsePatch, bodyBuffer);
|
||||
});
|
||||
|
||||
curl.on('error', function (err, code) {
|
||||
@ -584,27 +600,88 @@ export async function send (requestId: string, environmentId: string) {
|
||||
// Fetch some things
|
||||
const request = await models.request.getById(requestId);
|
||||
const settings = await models.settings.getOrCreate();
|
||||
|
||||
// This may throw
|
||||
const renderedRequest = await getRenderedRequest(request, environmentId);
|
||||
|
||||
// Get the workspace for the request
|
||||
const ancestors = await db.withAncestors(request, [
|
||||
models.requestGroup.type,
|
||||
models.workspace.type
|
||||
]);
|
||||
|
||||
if (!request) {
|
||||
throw new Error(`Failed to find request to send for ${requestId}`);
|
||||
}
|
||||
|
||||
const renderedRequestBeforePlugins = await getRenderedRequest(request, environmentId);
|
||||
|
||||
let renderedRequest: RenderedRequest;
|
||||
try {
|
||||
renderedRequest = await _applyRequestPluginHooks(renderedRequestBeforePlugins);
|
||||
} catch (err) {
|
||||
return {
|
||||
response: {
|
||||
url: renderedRequestBeforePlugins.url,
|
||||
parentId: renderedRequestBeforePlugins._id,
|
||||
error: err.message,
|
||||
statusCode: STATUS_CODE_PLUGIN_ERROR,
|
||||
statusMessage: err.plugin ? `Plugin ${err.plugin}` : 'Plugin',
|
||||
settingSendCookies: renderedRequestBeforePlugins.settingSendCookies,
|
||||
settingStoreCookies: renderedRequestBeforePlugins.settingStoreCookies
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const workspaceDoc = ancestors.find(doc => doc.type === models.workspace.type);
|
||||
const workspace = await models.workspace.getById(workspaceDoc ? workspaceDoc._id : 'n/a');
|
||||
if (!workspace) {
|
||||
throw new Error(`Failed to find workspace for request: ${requestId}`);
|
||||
}
|
||||
|
||||
// Render succeeded so we're good to go!
|
||||
return _actuallySend(renderedRequest, workspace, settings);
|
||||
}
|
||||
|
||||
function _getCurlHeader (curlHeadersObj: {[string]: string}, name: string, fallback: any): string {
|
||||
async function _applyRequestPluginHooks (renderedRequest: RenderedRequest): Promise<RenderedRequest> {
|
||||
let newRenderedRequest = renderedRequest;
|
||||
for (const {plugin, hook} of await plugins.getRequestHooks()) {
|
||||
newRenderedRequest = clone(newRenderedRequest);
|
||||
|
||||
const context = {
|
||||
...pluginContexts.app.init(plugin),
|
||||
...pluginContexts.request.init(plugin, newRenderedRequest)
|
||||
};
|
||||
|
||||
try {
|
||||
await hook(context);
|
||||
} catch (err) {
|
||||
err.plugin = plugin;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return newRenderedRequest;
|
||||
}
|
||||
|
||||
async function _applyResponsePluginHooks (
|
||||
response: ResponsePatch,
|
||||
bodyBuffer: ?Buffer = null
|
||||
): Promise<void> {
|
||||
for (const {plugin, hook} of await plugins.getResponseHooks()) {
|
||||
const context = {
|
||||
...pluginContexts.app.init(plugin),
|
||||
...pluginContexts.response.init(plugin, response, bodyBuffer)
|
||||
};
|
||||
|
||||
try {
|
||||
await hook(context);
|
||||
} catch (err) {
|
||||
err.plugin = plugin;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getCurlHeader (
|
||||
curlHeadersObj: {[string]: string},
|
||||
name: string,
|
||||
fallback: any
|
||||
): string {
|
||||
const headerName = Object.keys(curlHeadersObj).find(
|
||||
n => n.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
|
@ -11,6 +11,7 @@ const SCOPE = 'scope_123';
|
||||
const STATE = 'state_123';
|
||||
|
||||
describe('authorization_code', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('gets token with JSON and basic auth', async () => {
|
||||
createBWRedirectMock(`${REDIRECT_URI}?code=code_123&state=${STATE}`);
|
||||
window.fetch = jest.fn(() => new window.Response(
|
||||
|
@ -7,6 +7,7 @@ const CLIENT_SECRET = 'secret_12345456677756343';
|
||||
const SCOPE = 'scope_123';
|
||||
|
||||
describe('client_credentials', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('gets token with JSON and basic auth', async () => {
|
||||
window.fetch = jest.fn(() => new window.Response(
|
||||
JSON.stringify({access_token: 'token_123', token_type: 'token_type', scope: SCOPE}),
|
||||
|
@ -9,6 +9,7 @@ const SCOPE = 'scope_123';
|
||||
const STATE = 'state_123';
|
||||
|
||||
describe('implicit', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('works in default case', async () => {
|
||||
createBWRedirectMock(`${REDIRECT_URI}#access_token=token_123&state=${STATE}&foo=bar`);
|
||||
|
||||
|
@ -9,6 +9,7 @@ const PASSWORD = 'password';
|
||||
const SCOPE = 'scope_123';
|
||||
|
||||
describe('password', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('gets token with JSON and basic auth', async () => {
|
||||
window.fetch = jest.fn(() => new window.Response(
|
||||
JSON.stringify({access_token: 'token_123', token_type: 'token_type', scope: SCOPE}),
|
||||
|
36
app/plugins/context/__tests__/app.test.js
Normal file
36
app/plugins/context/__tests__/app.test.js
Normal file
@ -0,0 +1,36 @@
|
||||
import * as plugin from '../app';
|
||||
import * as modals from '../../../ui/components/modals';
|
||||
|
||||
const PLUGIN = {
|
||||
name: 'my-plugin',
|
||||
version: '1.0.0',
|
||||
directory: '/plugins/my-plugin',
|
||||
module: {}
|
||||
};
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('initializes correctly', () => {
|
||||
const result = plugin.init({name: PLUGIN});
|
||||
expect(Object.keys(result)).toEqual(['app']);
|
||||
expect(Object.keys(result.app)).toEqual(['alert', 'getPath']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('app.alert()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('shows alert with message', async () => {
|
||||
modals.showAlert = jest.fn().mockReturnValue('dummy-return-value');
|
||||
const result = plugin.init(PLUGIN);
|
||||
|
||||
// Make sure it returns result of showAlert()
|
||||
expect(result.app.alert()).toBe('dummy-return-value');
|
||||
expect(result.app.alert('My message')).toBe('dummy-return-value');
|
||||
|
||||
// Make sure it passes correct arguments
|
||||
expect(modals.showAlert.mock.calls).toEqual([
|
||||
[{message: '', title: 'Plugin my-plugin'}],
|
||||
[{message: 'My message', title: 'Plugin my-plugin'}]
|
||||
]);
|
||||
});
|
||||
});
|
99
app/plugins/context/__tests__/request.test.js
Normal file
99
app/plugins/context/__tests__/request.test.js
Normal file
@ -0,0 +1,99 @@
|
||||
import * as plugin from '../request';
|
||||
import * as models from '../../../models';
|
||||
|
||||
const PLUGIN = {
|
||||
name: 'my-plugin',
|
||||
version: '1.0.0',
|
||||
directory: '/plugins/my-plugin',
|
||||
module: {}
|
||||
};
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
await models.workspace.create({_id: 'wrk_1', name: 'My Workspace'});
|
||||
await models.request.create({_id: 'req_1', parentId: 'wrk_1', name: 'My Request'});
|
||||
});
|
||||
|
||||
it('initializes correctly', async () => {
|
||||
const result = plugin.init(PLUGIN, await models.request.getById('req_1'));
|
||||
expect(Object.keys(result)).toEqual(['request']);
|
||||
expect(Object.keys(result.request)).toEqual([
|
||||
'getId',
|
||||
'getName',
|
||||
'getUrl',
|
||||
'getMethod',
|
||||
'getHeader',
|
||||
'hasHeader',
|
||||
'removeHeader',
|
||||
'setHeader',
|
||||
'addHeader',
|
||||
'setCookie'
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails to initialize without request', () => {
|
||||
expect(() => plugin.init(PLUGIN))
|
||||
.toThrowError('contexts.request initialized without request');
|
||||
});
|
||||
});
|
||||
|
||||
describe('request.*', () => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
await models.workspace.create({_id: 'wrk_1', name: 'My Workspace'});
|
||||
await models.request.create({
|
||||
_id: 'req_1',
|
||||
parentId: 'wrk_1',
|
||||
name: 'My Request',
|
||||
headers: [
|
||||
{name: 'hello', value: 'world'},
|
||||
{name: 'Content-Type', value: 'application/json'}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('works for basic getters', async () => {
|
||||
const result = plugin.init(PLUGIN, await models.request.getById('req_1'));
|
||||
expect(result.request.getId()).toBe('req_1');
|
||||
expect(result.request.getName()).toBe('My Request');
|
||||
expect(result.request.getUrl()).toBe('');
|
||||
expect(result.request.getMethod()).toBe('GET');
|
||||
});
|
||||
|
||||
it('works for headers', async () => {
|
||||
const result = plugin.init(PLUGIN, await models.request.getById('req_1'));
|
||||
|
||||
// getHeader()
|
||||
expect(result.request.getHeader('content-type')).toBe('application/json');
|
||||
expect(result.request.getHeader('CONTENT-TYPE')).toBe('application/json');
|
||||
expect(result.request.getHeader('does-not-exist')).toBe(null);
|
||||
expect(result.request.hasHeader('Content-Type')).toBe(true);
|
||||
|
||||
// setHeader()
|
||||
result.request.setHeader('content-type', 'text/plain');
|
||||
expect(result.request.getHeader('Content-Type')).toBe('text/plain');
|
||||
|
||||
// addHeader()
|
||||
result.request.addHeader('content-type', 'new/type');
|
||||
result.request.addHeader('something-else', 'foo');
|
||||
expect(result.request.getHeader('Content-Type')).toBe('text/plain');
|
||||
expect(result.request.getHeader('something-else')).toBe('foo');
|
||||
|
||||
// removeHeader()
|
||||
result.request.removeHeader('content-type');
|
||||
expect(result.request.getHeader('Content-Type')).toBe(null);
|
||||
expect(result.request.hasHeader('Content-Type')).toBe(false);
|
||||
});
|
||||
|
||||
it('works for cookies', async () => {
|
||||
const request = await models.request.getById('req_1');
|
||||
request.cookies = []; // Because the plugin technically needs a RenderedRequest
|
||||
|
||||
const result = plugin.init(PLUGIN, request);
|
||||
|
||||
result.request.setCookie('foo', 'bar');
|
||||
result.request.setCookie('foo', 'baz');
|
||||
expect(request.cookies).toEqual([{name: 'foo', value: 'baz'}]);
|
||||
});
|
||||
});
|
58
app/plugins/context/__tests__/response.test.js
Normal file
58
app/plugins/context/__tests__/response.test.js
Normal file
@ -0,0 +1,58 @@
|
||||
import * as plugin from '../response';
|
||||
|
||||
const PLUGIN = {
|
||||
name: 'my-plugin',
|
||||
version: '1.0.0',
|
||||
directory: '/plugins/my-plugin',
|
||||
module: {}
|
||||
};
|
||||
|
||||
describe('init()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('initializes correctly', async () => {
|
||||
const result = plugin.init(PLUGIN, {});
|
||||
expect(Object.keys(result)).toEqual(['response']);
|
||||
expect(Object.keys(result.response)).toEqual([
|
||||
'getRequestId',
|
||||
'getStatusCode',
|
||||
'getStatusMessage',
|
||||
'getBytesRead',
|
||||
'getTime',
|
||||
'getBody'
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails to initialize without response', () => {
|
||||
expect(() => plugin.init(PLUGIN))
|
||||
.toThrowError('contexts.response initialized without response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('response.*', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('works for basic and full response', async () => {
|
||||
const response = {
|
||||
parentId: 'req_1',
|
||||
url: 'https://insomnia.rest',
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK',
|
||||
bytesRead: 123,
|
||||
elapsedTime: 321
|
||||
};
|
||||
const result = plugin.init(PLUGIN, response, Buffer.from('Hello World!'));
|
||||
expect(result.response.getRequestId()).toBe('req_1');
|
||||
expect(result.response.getStatusCode()).toBe(200);
|
||||
expect(result.response.getBytesRead()).toBe(123);
|
||||
expect(result.response.getTime()).toBe(321);
|
||||
expect(result.response.getBody().toString()).toBe('Hello World!');
|
||||
});
|
||||
|
||||
it('works for basic and empty response', async () => {
|
||||
const result = plugin.init(PLUGIN, {});
|
||||
expect(result.response.getRequestId()).toBe('');
|
||||
expect(result.response.getStatusCode()).toBe(0);
|
||||
expect(result.response.getBytesRead()).toBe(0);
|
||||
expect(result.response.getTime()).toBe(0);
|
||||
expect(result.response.getBody()).toBeNull();
|
||||
});
|
||||
});
|
22
app/plugins/context/app.js
Normal file
22
app/plugins/context/app.js
Normal file
@ -0,0 +1,22 @@
|
||||
// @flow
|
||||
import type {Plugin} from '../';
|
||||
import * as electron from 'electron';
|
||||
import {showAlert} from '../../ui/components/modals/index';
|
||||
|
||||
export function init (plugin: Plugin): {app: Object} {
|
||||
return {
|
||||
app: {
|
||||
alert (message: string): Promise<void> {
|
||||
return showAlert({title: `Plugin ${plugin.name}`, message: message || ''});
|
||||
},
|
||||
getPath (name: string): string {
|
||||
switch (name.toLowerCase()) {
|
||||
case 'desktop':
|
||||
return electron.remote.app.getPath('desktop');
|
||||
default:
|
||||
throw new Error(`Unknown path name ${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
8
app/plugins/context/index.js
Normal file
8
app/plugins/context/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
import * as _app from './app';
|
||||
import * as _request from './request';
|
||||
import * as _response from './response';
|
||||
|
||||
export const app = _app;
|
||||
export const request = _request;
|
||||
export const response = _response;
|
74
app/plugins/context/request.js
Normal file
74
app/plugins/context/request.js
Normal file
@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
import type {Plugin} from '../';
|
||||
import type {RenderedRequest} from '../../common/render';
|
||||
import * as misc from '../../common/misc';
|
||||
|
||||
export function init (plugin: Plugin, renderedRequest: RenderedRequest): {request: Object} {
|
||||
if (!renderedRequest) {
|
||||
throw new Error('contexts.request initialized without request');
|
||||
}
|
||||
|
||||
return {
|
||||
request: {
|
||||
getId (): string {
|
||||
return renderedRequest._id;
|
||||
},
|
||||
getName (): string {
|
||||
return renderedRequest.name;
|
||||
},
|
||||
getUrl (): string {
|
||||
// TODO: Get full URL, including querystring
|
||||
return renderedRequest.url;
|
||||
},
|
||||
getMethod (): string {
|
||||
return renderedRequest.method;
|
||||
},
|
||||
getHeader (name: string): string | null {
|
||||
const headers = misc.filterHeaders(renderedRequest.headers, name);
|
||||
if (headers.length) {
|
||||
// Use the last header if there are multiple of the same
|
||||
const header = headers[headers.length - 1];
|
||||
return header.value || '';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
hasHeader (name: string): boolean {
|
||||
return this.getHeader(name) !== null;
|
||||
},
|
||||
removeHeader (name: string): void {
|
||||
const headers = misc.filterHeaders(renderedRequest.headers, name);
|
||||
renderedRequest.headers = renderedRequest.headers.filter(
|
||||
h => !headers.includes(h)
|
||||
);
|
||||
},
|
||||
setHeader (name: string, value: string): void {
|
||||
const header = misc.filterHeaders(renderedRequest.headers, name)[0];
|
||||
if (header) {
|
||||
header.value = value;
|
||||
} else {
|
||||
this.addHeader(name, value);
|
||||
}
|
||||
},
|
||||
addHeader (name: string, value: string): void {
|
||||
const header = misc.filterHeaders(renderedRequest.headers, name)[0];
|
||||
if (!header) {
|
||||
renderedRequest.headers.push({name, value});
|
||||
}
|
||||
},
|
||||
setCookie (name: string, value: string): void {
|
||||
const cookie = renderedRequest.cookies.find(c => c.name === name);
|
||||
if (cookie) {
|
||||
cookie.value = value;
|
||||
} else {
|
||||
renderedRequest.cookies.push({name, value});
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: For these to make sense, we'd need to account for cookies in the jar as well
|
||||
// addCookie (name: string, value: string): void {}
|
||||
// getCookie (name: string): string | null {}
|
||||
// removeCookie (name: string): void {}
|
||||
}
|
||||
};
|
||||
}
|
48
app/plugins/context/response.js
Normal file
48
app/plugins/context/response.js
Normal file
@ -0,0 +1,48 @@
|
||||
// @flow
|
||||
import type {Plugin} from '../';
|
||||
|
||||
type MaybeResponse = {
|
||||
parentId?: string,
|
||||
statusCode?: number,
|
||||
statusMessage?: string,
|
||||
bytesRead?: number,
|
||||
elapsedTime?: number,
|
||||
}
|
||||
|
||||
export function init (
|
||||
plugin: Plugin,
|
||||
response: MaybeResponse,
|
||||
bodyBuffer: Buffer | null = null
|
||||
): {response: Object} {
|
||||
if (!response) {
|
||||
throw new Error('contexts.response initialized without response');
|
||||
}
|
||||
|
||||
return {
|
||||
response: {
|
||||
// TODO: Make this work. Right now it doesn't because _id is
|
||||
// not generated in network.js
|
||||
// getId () {
|
||||
// return response.parentId;
|
||||
// },
|
||||
getRequestId (): string {
|
||||
return response.parentId || '';
|
||||
},
|
||||
getStatusCode (): number {
|
||||
return response.statusCode || 0;
|
||||
},
|
||||
getStatusMessage (): string {
|
||||
return response.statusMessage || '';
|
||||
},
|
||||
getBytesRead (): number {
|
||||
return response.bytesRead || 0;
|
||||
},
|
||||
getTime (): number {
|
||||
return response.elapsedTime || 0;
|
||||
},
|
||||
getBody (): Buffer | null {
|
||||
return bodyBuffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,44 +1,84 @@
|
||||
// @flow
|
||||
import mkdirp from 'mkdirp';
|
||||
import * as models from '../models';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {PLUGIN_PATHS} from '../common/constants';
|
||||
import {render} from '../templating';
|
||||
import skeletonPackageJson from './skeleton/package.json.js';
|
||||
import skeletonPluginJs from './skeleton/plugin.js.js';
|
||||
import {resolveHomePath} from '../common/misc';
|
||||
|
||||
let plugins = null;
|
||||
export type Plugin = {
|
||||
name: string,
|
||||
version: string,
|
||||
directory: string,
|
||||
module: *
|
||||
};
|
||||
|
||||
export async function init () {
|
||||
// Force plugins to load.
|
||||
getPlugins(true);
|
||||
export type TemplateTag = {
|
||||
plugin: string,
|
||||
templateTag: Function
|
||||
}
|
||||
|
||||
export function getPlugins (force = false) {
|
||||
if (!plugins || force) {
|
||||
// Make sure the directories exist
|
||||
export type RequestHook = {
|
||||
plugin: Plugin,
|
||||
hook: Function
|
||||
}
|
||||
|
||||
export type ResponseHook = {
|
||||
plugin: Plugin,
|
||||
hook: Function
|
||||
}
|
||||
|
||||
let plugins: ?Array<Plugin> = null;
|
||||
|
||||
export async function init (): Promise<void> {
|
||||
// Force plugins to load.
|
||||
await getPlugins(true);
|
||||
}
|
||||
|
||||
export async function getPlugins (force: boolean = false): Promise<Array<Plugin>> {
|
||||
if (force) {
|
||||
plugins = null;
|
||||
}
|
||||
|
||||
if (!plugins) {
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const extraPaths = settings.pluginPath.split(':').filter(p => p).map(resolveHomePath);
|
||||
const allPaths = [...PLUGIN_PATHS, ...extraPaths];
|
||||
|
||||
// Make sure the default directories exist
|
||||
for (const p of PLUGIN_PATHS) {
|
||||
mkdirp.sync(p);
|
||||
}
|
||||
|
||||
plugins = [];
|
||||
for (const p of PLUGIN_PATHS) {
|
||||
for (const p of allPaths) {
|
||||
for (const dir of fs.readdirSync(p)) {
|
||||
if (dir.indexOf('.') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const moduleDirectory = path.join(p, dir);
|
||||
const modulePath = path.join(p, dir);
|
||||
const packageJSONPath = path.join(modulePath, 'package.json');
|
||||
|
||||
// Use global.require() instead of require() because Webpack wraps require()
|
||||
const pluginJson = global.require(path.join(moduleDirectory, 'package.json'));
|
||||
const module = global.require(moduleDirectory);
|
||||
delete global.require.cache[global.require.resolve(packageJSONPath)];
|
||||
const pluginJson = global.require(packageJSONPath);
|
||||
|
||||
// Delete require cache entry and re-require
|
||||
delete global.require.cache[global.require.resolve(modulePath)];
|
||||
const module = global.require(modulePath);
|
||||
|
||||
plugins.push({
|
||||
name: pluginJson.name,
|
||||
version: pluginJson.version || '0.0.0',
|
||||
directory: moduleDirectory,
|
||||
directory: modulePath,
|
||||
module
|
||||
});
|
||||
|
||||
// console.log(`[plugin] Loaded ${modulePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,7 +86,7 @@ export function getPlugins (force = false) {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
export async function createPlugin (displayName) {
|
||||
export async function createPlugin (displayName: string): Promise<void> {
|
||||
// Create root plugin dir
|
||||
const name = displayName.replace(/\s/g, '-').toLowerCase();
|
||||
const dir = path.join(PLUGIN_PATHS[0], name);
|
||||
@ -58,12 +98,42 @@ export async function createPlugin (displayName) {
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), renderedPackageJson);
|
||||
}
|
||||
|
||||
export function getTemplateTags () {
|
||||
export async function getTemplateTags (): Promise<Array<TemplateTag>> {
|
||||
console.log('GETTING TEMPLATE TAGS');
|
||||
let extensions = [];
|
||||
for (const plugin of getPlugins()) {
|
||||
for (const plugin of await getPlugins()) {
|
||||
const templateTags = plugin.module.templateTags || [];
|
||||
extensions = [...extensions, ...templateTags];
|
||||
extensions = [
|
||||
...extensions,
|
||||
...templateTags.map(tt => ({plugin: plugin.name, templateTag: tt}))
|
||||
];
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
export async function getRequestHooks (): Promise<Array<RequestHook>> {
|
||||
let functions = [];
|
||||
for (const plugin of await getPlugins()) {
|
||||
const moreFunctions = plugin.module.requestHooks || [];
|
||||
functions = [
|
||||
...functions,
|
||||
...moreFunctions.map(hook => ({plugin, hook}))
|
||||
];
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
||||
export async function getResponseHooks (): Promise<Array<ResponseHook>> {
|
||||
let functions = [];
|
||||
for (const plugin of await getPlugins()) {
|
||||
const moreFunctions = plugin.module.responseHooks || [];
|
||||
functions = [
|
||||
...functions,
|
||||
...moreFunctions.map(hook => ({plugin, hook}))
|
||||
];
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
@ -1,4 +1,10 @@
|
||||
module.exports = `
|
||||
module.exports.preRequestHooks = [
|
||||
context => {
|
||||
context.addHeader('foo', 'bar');
|
||||
}
|
||||
];
|
||||
|
||||
module.exports.templateTags = [{
|
||||
name: 'hello',
|
||||
displayName: 'Say Hello',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as crypt from '../crypt';
|
||||
|
||||
describe('deriveKey()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('derives a key properly', async () => {
|
||||
const result = await crypt.deriveKey('Password', 'email', 'salt');
|
||||
|
||||
@ -11,6 +12,7 @@ describe('deriveKey()', () => {
|
||||
});
|
||||
|
||||
describe('encryptRSA', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('encrypts and decrypts', () => {
|
||||
const resultEncrypted = crypt.encryptAES('rawkey', 'Hello World!', 'additional data');
|
||||
const resultDecrypted = crypt.decryptAES('rawkey', resultEncrypted);
|
||||
|
@ -7,6 +7,8 @@ import * as crypt from '../crypt';
|
||||
|
||||
describe('Test push/pull behaviour', () => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
|
||||
// Reset some things
|
||||
sync._testReset();
|
||||
await _setSessionData();
|
||||
@ -15,7 +17,6 @@ describe('Test push/pull behaviour', () => {
|
||||
// Init sync and storage
|
||||
const config = {inMemoryOnly: true, autoload: false, filename: null};
|
||||
await syncStorage.initDB(config, true);
|
||||
await db.init(models.types(), config, true);
|
||||
|
||||
// Add some data
|
||||
await models.workspace.create({_id: 'wrk_1', name: 'Workspace 1'});
|
||||
@ -226,6 +227,8 @@ describe('Test push/pull behaviour', () => {
|
||||
|
||||
describe('Integration tests for creating Resources and pushing', () => {
|
||||
beforeEach(async () => {
|
||||
await global.insomniaBeforeEach();
|
||||
|
||||
// Reset some things
|
||||
await _setSessionData();
|
||||
sync._testReset();
|
||||
@ -237,7 +240,6 @@ describe('Integration tests for creating Resources and pushing', () => {
|
||||
// Init storage
|
||||
const config = {inMemoryOnly: true, autoload: false, filename: null};
|
||||
await syncStorage.initDB(config, true);
|
||||
await db.init(models.types(), config, true);
|
||||
|
||||
// Add some data
|
||||
await models.workspace.create({_id: 'wrk_empty', name: 'Workspace Empty'});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as utils from '../utils';
|
||||
|
||||
describe('getKeys()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('flattens complex object', () => {
|
||||
const obj = {
|
||||
foo: 'bar',
|
||||
@ -48,6 +49,7 @@ describe('getKeys()', () => {
|
||||
});
|
||||
|
||||
describe('tokenizeTag()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('tokenizes complex tag', () => {
|
||||
const actual = utils.tokenizeTag(
|
||||
`{% name bar, "baz \\"qux\\"" , 1 + 5 | default("foo") %}`
|
||||
@ -149,6 +151,7 @@ describe('tokenizeTag()', () => {
|
||||
});
|
||||
|
||||
describe('unTokenizeTag()', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('untokenizes a tag', () => {
|
||||
const tagStr = `{% name bar, "baz \\"qux\\"" , 1 + 5, 'hi' %}`;
|
||||
|
||||
|
@ -19,6 +19,7 @@ function assertTemplateFails (txt, expected) {
|
||||
}
|
||||
|
||||
describe('Base64EncodeExtension', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('encodes nothing', assertTemplate("{% base64 'encode' %}", ''));
|
||||
it('encodes something', assertTemplate("{% base64 'encode', 'my string' %}", 'bXkgc3RyaW5n'));
|
||||
it('decodes nothing', assertTemplate("{% base64 'decode' %}", ''));
|
||||
|
@ -23,6 +23,7 @@ const secondsRe = /^\d{10}$/;
|
||||
const millisRe = /^\d{13}$/;
|
||||
|
||||
describe('NowExtension', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders default ISO', assertTemplate('{% now %}', isoRe));
|
||||
it('renders ISO-8601', assertTemplate('{% now "ISO-8601" %}', isoRe));
|
||||
it('renders seconds', assertTemplate('{% now "seconds" %}', secondsRe));
|
||||
|
@ -1,12 +1,10 @@
|
||||
import * as templating from '../../index';
|
||||
import * as db from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
import {cookiesFromJar, jarFromCookies} from '../../../common/cookies';
|
||||
import {getRenderContext} from '../../../common/render';
|
||||
|
||||
describe('RequestExtension cookie', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should get cookie by name', async () => {
|
||||
// Create necessary models
|
||||
const workspace = await models.workspace.create({name: 'Workspace'});
|
||||
@ -33,8 +31,7 @@ describe('RequestExtension cookie', async () => {
|
||||
});
|
||||
|
||||
describe('RequestExtension url', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should get url', async () => {
|
||||
// Create necessary models
|
||||
const workspace = await models.workspace.create({name: 'Workspace'});
|
||||
@ -68,8 +65,7 @@ describe('RequestExtension url', async () => {
|
||||
});
|
||||
|
||||
describe('RequestExtension header', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('should get url', async () => {
|
||||
// Create necessary models
|
||||
const workspace = await models.workspace.create({name: 'Workspace'});
|
||||
|
@ -1,9 +1,8 @@
|
||||
import * as templating from '../../index';
|
||||
import * as db from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
|
||||
describe('ResponseExtension General', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('fails on no responses', async () => {
|
||||
const request = await models.request.create({parentId: 'foo'});
|
||||
|
||||
@ -39,8 +38,7 @@ describe('ResponseExtension General', async () => {
|
||||
});
|
||||
|
||||
describe('ResponseExtension JSONPath', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders basic response "body", query', async () => {
|
||||
const request = await models.request.create({parentId: 'foo'});
|
||||
await models.response.create({
|
||||
@ -115,8 +113,7 @@ describe('ResponseExtension JSONPath', async () => {
|
||||
});
|
||||
|
||||
describe('ResponseExtension XPath', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders basic response "body", query', async () => {
|
||||
const request = await models.request.create({parentId: 'foo'});
|
||||
await models.response.create({
|
||||
@ -191,8 +188,7 @@ describe('ResponseExtension XPath', async () => {
|
||||
});
|
||||
|
||||
describe('ResponseExtension Header', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders basic response "header"', async () => {
|
||||
const request = await models.request.create({parentId: 'foo'});
|
||||
await models.response.create({
|
||||
@ -237,8 +233,7 @@ describe('ResponseExtension Header', async () => {
|
||||
});
|
||||
|
||||
describe('ResponseExtension Raw', async () => {
|
||||
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
|
||||
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders basic response "header"', async () => {
|
||||
const request = await models.request.create({parentId: 'foo'});
|
||||
await models.response.create({
|
||||
|
@ -10,5 +10,6 @@ function assertTemplate (txt, expected) {
|
||||
const millisRe = /^\d{13}$/;
|
||||
|
||||
describe('TimestampExtension', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders basic', assertTemplate('{% timestamp %}', millisRe));
|
||||
});
|
||||
|
@ -19,6 +19,7 @@ function assertTemplateFails (txt, expected) {
|
||||
}
|
||||
|
||||
describe('UuidExtension', () => {
|
||||
beforeEach(global.insomniaBeforeEach);
|
||||
it('renders default v4', assertTemplate('{% uuid %}', 'dd2ccc1a-2745-477a-881a-9e8ef9d42403'));
|
||||
it('renders 4', assertTemplate('{% uuid "4" %}', 'e3e96e5f-dd68-4229-8b66-dee1f0940f3d'));
|
||||
it('renders 4 num', assertTemplate('{% uuid 4 %}', 'a262d22b-5fa8-491c-9bd9-58fba03e301e'));
|
||||
|
@ -16,6 +16,10 @@ const DEFAULT_EXTENSIONS = [
|
||||
responseExtension
|
||||
];
|
||||
|
||||
export function all () {
|
||||
return [...DEFAULT_EXTENSIONS, ...plugins.getTemplateTags()];
|
||||
export async function all () {
|
||||
const templateTags = await plugins.getTemplateTags();
|
||||
return [
|
||||
...DEFAULT_EXTENSIONS,
|
||||
...templateTags.map(p => p.templateTag)
|
||||
];
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ export function render (text: string, config: Object = {}): Promise<string> {
|
||||
const path = config.path || null;
|
||||
const renderMode = config.renderMode || RENDER_ALL;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const nj = getNunjucks(renderMode);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const nj = await getNunjucks(renderMode);
|
||||
|
||||
nj.renderString(text, context, (err, result) => {
|
||||
if (err) {
|
||||
@ -85,8 +85,8 @@ export function reload (): void {
|
||||
/**
|
||||
* Get definitions of template tags
|
||||
*/
|
||||
export function getTagDefinitions () {
|
||||
const env = getNunjucks();
|
||||
export async function getTagDefinitions (): Promise<Array<NunjucksTag>> {
|
||||
const env = await getNunjucks(RENDER_ALL);
|
||||
|
||||
return Object.keys(env.extensions)
|
||||
.map(k => env.extensions[k])
|
||||
@ -100,7 +100,7 @@ export function getTagDefinitions () {
|
||||
}));
|
||||
}
|
||||
|
||||
function getNunjucks (renderMode) {
|
||||
async function getNunjucks (renderMode: string) {
|
||||
if (renderMode === RENDER_VARS && nunjucksVariablesOnly) {
|
||||
return nunjucksVariablesOnly;
|
||||
}
|
||||
@ -148,7 +148,7 @@ function getNunjucks (renderMode) {
|
||||
|
||||
const nj = nunjucks.configure(config);
|
||||
|
||||
const allExtensions = extensions.all();
|
||||
const allExtensions = await extensions.all();
|
||||
for (let i = 0; i < allExtensions.length; i++) {
|
||||
const ext = allExtensions[i];
|
||||
ext.priority = ext.priority || i * 100;
|
||||
@ -158,7 +158,6 @@ function getNunjucks (renderMode) {
|
||||
// Hidden helper filter to debug complicated things
|
||||
// eg. `{{ foo | urlencode | debug | upper }}`
|
||||
nj.addFilter('debug', o => {
|
||||
console.log('DEBUG', {o}, JSON.stringify(o));
|
||||
return o;
|
||||
});
|
||||
}
|
||||
|
@ -410,9 +410,9 @@ class CodeEditor extends PureComponent {
|
||||
};
|
||||
|
||||
// Only allow tags if we have variables too
|
||||
getTags = () => {
|
||||
getTags = async () => {
|
||||
const expandedTags = [];
|
||||
for (const tagDef of getTagDefinitions()) {
|
||||
for (const tagDef of await getTagDefinitions()) {
|
||||
if (tagDef.args[0].type !== 'enum') {
|
||||
expandedTags.push(tagDef);
|
||||
continue;
|
||||
|
@ -225,7 +225,7 @@ async function _updateElementText (render, mark, text) {
|
||||
|
||||
if (tagMatch) {
|
||||
const tagData = tokenizeTag(str);
|
||||
const tagDefinition = getTagDefinitions().find(d => d.name === tagData.name);
|
||||
const tagDefinition = (await getTagDefinitions()).find(d => d.name === tagData.name);
|
||||
|
||||
if (tagDefinition) {
|
||||
// Try rendering these so we can show errors if needed
|
||||
|
@ -21,7 +21,7 @@ class MethodDropdown extends PureComponent {
|
||||
// Prompt user for the method
|
||||
showPrompt({
|
||||
defaultValue: this.props.method,
|
||||
headerName: 'HTTP Method',
|
||||
title: 'HTTP Method',
|
||||
submitName: 'Done',
|
||||
upperCase: true,
|
||||
selectText: true,
|
||||
|
@ -18,7 +18,7 @@ class RequestGroupActionsDropdown extends PureComponent {
|
||||
const {requestGroup} = this.props;
|
||||
|
||||
showPrompt({
|
||||
headerName: 'Rename Folder',
|
||||
title: 'Rename Folder',
|
||||
defaultValue: requestGroup.name,
|
||||
onComplete: name => {
|
||||
models.requestGroup.update(requestGroup, {name});
|
||||
|
@ -63,7 +63,7 @@ class WorkspaceDropdown extends PureComponent {
|
||||
|
||||
_handleWorkspaceCreate (noTrack) {
|
||||
showPrompt({
|
||||
headerName: 'Create New Workspace',
|
||||
title: 'Create New Workspace',
|
||||
defaultValue: 'My Workspace',
|
||||
submitName: 'Create',
|
||||
selectText: true,
|
||||
|
@ -13,7 +13,7 @@ class PromptModal extends PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
headerName: 'Not Set',
|
||||
title: 'Not Set',
|
||||
defaultValue: '',
|
||||
submitName: 'Not Set',
|
||||
selectText: false,
|
||||
@ -60,7 +60,7 @@ class PromptModal extends PureComponent {
|
||||
|
||||
show (options) {
|
||||
const {
|
||||
headerName,
|
||||
title,
|
||||
defaultValue,
|
||||
submitName,
|
||||
selectText,
|
||||
@ -87,7 +87,7 @@ class PromptModal extends PureComponent {
|
||||
this._onDeleteHint = onDeleteHint;
|
||||
|
||||
this.setState({
|
||||
headerName,
|
||||
title,
|
||||
defaultValue,
|
||||
submitName,
|
||||
selectText,
|
||||
@ -125,7 +125,7 @@ class PromptModal extends PureComponent {
|
||||
render () {
|
||||
const {
|
||||
submitName,
|
||||
headerName,
|
||||
title,
|
||||
hint,
|
||||
inputType,
|
||||
placeholder,
|
||||
@ -153,7 +153,7 @@ class PromptModal extends PureComponent {
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader>{headerName}</ModalHeader>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalBody className="wide">
|
||||
<form onSubmit={this._handleSubmit} className="wide pad">
|
||||
<div className="form-control form-control--outlined form-control--wide">
|
||||
|
@ -51,7 +51,7 @@ class WorkspaceShareSettingsModal extends PureComponent {
|
||||
|
||||
_handleShareWithTeam (team) {
|
||||
showPrompt({
|
||||
headerName: 'Share Workspace',
|
||||
title: 'Share Workspace',
|
||||
label: 'Confirm password to share workspace',
|
||||
placeholder: '•••••••••••••••••',
|
||||
submitName: 'Share with Team',
|
||||
|
@ -134,7 +134,7 @@ class RequestUrlBar extends PureComponent {
|
||||
_handleSendAfterDelay () {
|
||||
showPrompt({
|
||||
inputType: 'decimal',
|
||||
headerName: 'Send After Delay',
|
||||
title: 'Send After Delay',
|
||||
label: 'Delay in seconds',
|
||||
defaultValue: 3,
|
||||
submitName: 'Start',
|
||||
@ -151,7 +151,7 @@ class RequestUrlBar extends PureComponent {
|
||||
_handleSendOnInterval () {
|
||||
showPrompt({
|
||||
inputType: 'decimal',
|
||||
headerName: 'Send on Interval',
|
||||
title: 'Send on Interval',
|
||||
label: 'Interval in seconds',
|
||||
defaultValue: 3,
|
||||
submitName: 'Start',
|
||||
|
@ -202,6 +202,23 @@ class General extends PureComponent {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="pad-top"/>
|
||||
|
||||
<h2>Plugins</h2>
|
||||
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Additional Plugin Path <HelpTooltip>Tell Insomnia to look for plugins in a different
|
||||
directory</HelpTooltip>
|
||||
<input placeholder="~/.insomnia:/other/path"
|
||||
name="pluginPath"
|
||||
type="text"
|
||||
defaultValue={settings.pluginPath}
|
||||
onChange={this._handleUpdateSetting}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import {showPrompt} from '../modals/index';
|
||||
class ImportExport extends PureComponent {
|
||||
_handleImportUri () {
|
||||
showPrompt({
|
||||
headerName: 'Import Data from URL',
|
||||
title: 'Import Data from URL',
|
||||
submitName: 'Fetch and Import',
|
||||
label: 'URL',
|
||||
placeholder: 'https://website.com/insomnia-import.json',
|
||||
|
@ -1,29 +1,50 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
// @flow
|
||||
import type {Plugin} from '../../../plugins/index';
|
||||
import {createPlugin, getPlugins} from '../../../plugins/index';
|
||||
import React from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import * as electron from 'electron';
|
||||
import {createPlugin, getPlugins} from '../../../plugins/index';
|
||||
import Button from '../base/button';
|
||||
import CopyButton from '../base/copy-button';
|
||||
import {showPrompt} from '../modals/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import {reload} from '../../../templating/index';
|
||||
|
||||
type DefaultProps = void;
|
||||
type Props = void;
|
||||
type State = {
|
||||
plugins: Array<Plugin>,
|
||||
npmPluginValue: string
|
||||
};
|
||||
|
||||
@autobind
|
||||
class Plugins extends PureComponent {
|
||||
constructor (props) {
|
||||
class Plugins extends React.PureComponent<DefaultProps, Props, State> {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
plugins: getPlugins(true)
|
||||
plugins: [],
|
||||
npmPluginValue: ''
|
||||
};
|
||||
}
|
||||
|
||||
_handleOpenDirectory (directory) {
|
||||
_handleAddNpmPluginChange (e: Event & {target: HTMLButtonElement}) {
|
||||
this.setState({npmPluginValue: e.target.value});
|
||||
}
|
||||
|
||||
_handleAddFromNpm () {
|
||||
console.log('ADD FROM NPM', this.state.npmPluginValue);
|
||||
}
|
||||
|
||||
_handleOpenDirectory (directory: string) {
|
||||
electron.remote.shell.showItemInFolder(directory);
|
||||
}
|
||||
|
||||
_handleGeneratePlugin () {
|
||||
showPrompt({
|
||||
headerName: 'Plugin Name',
|
||||
title: 'Plugin Name',
|
||||
defaultValue: 'My Plugin',
|
||||
submitName: 'Generate Plugin',
|
||||
selectText: true,
|
||||
@ -31,21 +52,25 @@ class Plugins extends PureComponent {
|
||||
label: 'Plugin Name',
|
||||
onComplete: async name => {
|
||||
await createPlugin(name);
|
||||
this._handleRefreshPlugins();
|
||||
await this._handleRefreshPlugins();
|
||||
trackEvent('Plugins', 'Generate');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleRefreshPlugins () {
|
||||
async _handleRefreshPlugins () {
|
||||
// Get and reload plugins
|
||||
const plugins = getPlugins(true);
|
||||
const plugins = await getPlugins(true);
|
||||
reload();
|
||||
|
||||
this.setState({plugins});
|
||||
trackEvent('Plugins', 'Refresh');
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._handleRefreshPlugins();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {plugins} = this.state;
|
||||
|
||||
@ -85,19 +110,32 @@ class Plugins extends PureComponent {
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-right">
|
||||
<button className="btn btn--clicky" onClick={this._handleRefreshPlugins}>
|
||||
Reload Plugins
|
||||
</button>
|
||||
{' '}
|
||||
<button className="btn btn--clicky" onClick={this._handleGeneratePlugin}>
|
||||
Generate New Plugin
|
||||
</button>
|
||||
</p>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<input onChange={this._handleAddNpmPluginChange}
|
||||
type="text"
|
||||
placeholder="insomnia-foo-bar"/>
|
||||
</div>
|
||||
<div className="form-control width-auto">
|
||||
<button className="btn btn--clicky" onClick={this._handleAddFromNpm}>
|
||||
Add From NPM
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-control width-auto">
|
||||
<button className="btn btn--clicky" onClick={this._handleRefreshPlugins}>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-control width-auto">
|
||||
<button className="btn btn--clicky" onClick={this._handleGeneratePlugin}>
|
||||
New Plugin
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Plugins.propTypes = {};
|
||||
|
||||
export default Plugins;
|
||||
|
@ -40,6 +40,7 @@ class StatusTag extends PureComponent {
|
||||
default:
|
||||
colorClass = 'bg-danger';
|
||||
genericStatusMessage = 'UNKNOWN';
|
||||
statusCodeToDisplay = '';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import clone from 'clone';
|
||||
import * as templating from '../../../templating';
|
||||
import * as templateUtils from '../../../templating/utils';
|
||||
import * as db from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
import HelpTooltip from '../help-tooltip';
|
||||
import {fnOrString} from '../../../common/misc';
|
||||
import {delay, fnOrString} from '../../../common/misc';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
|
||||
@autobind
|
||||
@ -14,29 +15,31 @@ class TagEditor extends PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const activeTagData = templateUtils.tokenizeTag(props.defaultValue);
|
||||
|
||||
const tagDefinitions = templating.getTagDefinitions();
|
||||
const activeTagDefinition = tagDefinitions.find(d => d.name === activeTagData.name);
|
||||
|
||||
// Edit tags raw that we don't know about
|
||||
if (!activeTagDefinition) {
|
||||
activeTagData.rawValue = props.defaultValue;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
activeTagData,
|
||||
activeTagDefinition,
|
||||
loadingDocs: true,
|
||||
activeTagData: null,
|
||||
activeTagDefinition: null,
|
||||
tagDefinitions: [],
|
||||
loadingDocs: false,
|
||||
allDocs: {},
|
||||
rendering: true,
|
||||
preview: '',
|
||||
error: ''
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount () {
|
||||
async componentDidMount () {
|
||||
const activeTagData = templateUtils.tokenizeTag(this.props.defaultValue);
|
||||
|
||||
const tagDefinitions = await templating.getTagDefinitions();
|
||||
const activeTagDefinition = tagDefinitions.find(d => d.name === activeTagData.name);
|
||||
|
||||
// Edit tags raw that we don't know about
|
||||
if (!activeTagDefinition) {
|
||||
activeTagData.rawValue = this.props.defaultValue;
|
||||
}
|
||||
|
||||
await this._refreshModels(this.props.workspace);
|
||||
await this._update(this.state.activeTagDefinition, this.state.activeTagData, true);
|
||||
await this._update(tagDefinitions, activeTagDefinition, activeTagData, true);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@ -47,6 +50,15 @@ class TagEditor extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_handleRefresh () {
|
||||
this._update(
|
||||
this.state.tagDefinitions,
|
||||
this.state.activeTagDefinition,
|
||||
this.state.activeTagData,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
async _refreshModels (workspace) {
|
||||
const allDocs = {};
|
||||
for (const type of models.types()) {
|
||||
@ -61,12 +73,12 @@ class TagEditor extends PureComponent {
|
||||
}
|
||||
|
||||
_updateArg (argValue, argIndex) {
|
||||
const {activeTagData, activeTagDefinition} = this.state;
|
||||
const {tagDefinitions, activeTagData, activeTagDefinition} = this.state;
|
||||
|
||||
const tagData = clone(activeTagData);
|
||||
tagData.args[argIndex].value = argValue;
|
||||
|
||||
this._update(activeTagDefinition, tagData, false);
|
||||
this._update(tagDefinitions, activeTagDefinition, tagData, false);
|
||||
}
|
||||
|
||||
_handleChange (e) {
|
||||
@ -81,18 +93,18 @@ class TagEditor extends PureComponent {
|
||||
}
|
||||
|
||||
_handleChangeCustomArg (e) {
|
||||
const {activeTagData, activeTagDefinition} = this.state;
|
||||
const {tagDefinitions, activeTagData, activeTagDefinition} = this.state;
|
||||
|
||||
const tagData = clone(activeTagData);
|
||||
tagData.rawValue = e.target.value;
|
||||
|
||||
this._update(activeTagDefinition, tagData, false);
|
||||
this._update(tagDefinitions, activeTagDefinition, tagData, false);
|
||||
}
|
||||
|
||||
_handleChangeTag (e) {
|
||||
async _handleChangeTag (e) {
|
||||
const name = e.target.value;
|
||||
const tagDefinition = templating.getTagDefinitions().find(d => d.name === name);
|
||||
this._update(tagDefinition, false);
|
||||
const tagDefinition = (await templating.getTagDefinitions()).find(d => d.name === name);
|
||||
this._update(this.state.tagDefinitions, tagDefinition, false);
|
||||
trackEvent('Tag Editor', 'Change Tag', name);
|
||||
}
|
||||
|
||||
@ -114,8 +126,13 @@ class TagEditor extends PureComponent {
|
||||
return templateUtils.tokenizeTag(defaultFill);
|
||||
}
|
||||
|
||||
async _update (tagDefinition, tagData, noCallback = false) {
|
||||
async _update (tagDefinitions, tagDefinition, tagData, noCallback = false) {
|
||||
const {handleRender} = this.props;
|
||||
this.setState({rendering: true});
|
||||
|
||||
// Start render loader
|
||||
const start = Date.now();
|
||||
this.setState({rendering: true});
|
||||
|
||||
let preview = '';
|
||||
let error = '';
|
||||
@ -140,15 +157,17 @@ class TagEditor extends PureComponent {
|
||||
error = err.message;
|
||||
}
|
||||
|
||||
const isMounted = !!this._select;
|
||||
if (isMounted) {
|
||||
this.setState({
|
||||
activeTagData,
|
||||
preview,
|
||||
error,
|
||||
activeTagDefinition: tagDefinition
|
||||
});
|
||||
}
|
||||
// Make rendering take at least this long so we can see a spinner
|
||||
await delay(300 - (Date.now() - start));
|
||||
|
||||
this.setState({
|
||||
tagDefinitions,
|
||||
activeTagData,
|
||||
preview,
|
||||
error,
|
||||
rendering: false,
|
||||
activeTagDefinition: tagDefinition
|
||||
});
|
||||
|
||||
// Call the callback if we need to
|
||||
if (!noCallback) {
|
||||
@ -278,7 +297,26 @@ class TagEditor extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {error, preview, activeTagDefinition, activeTagData} = this.state;
|
||||
const {error, preview, activeTagDefinition, activeTagData, rendering} = this.state;
|
||||
|
||||
if (!activeTagData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let previewElement;
|
||||
if (error) {
|
||||
previewElement = (
|
||||
<code className="block danger selectable">{error || <span> </span>}</code>
|
||||
);
|
||||
} else if (rendering) {
|
||||
previewElement = (
|
||||
<code className="block"><span className="faint italic">rendering...</span></code>
|
||||
);
|
||||
} else {
|
||||
previewElement = (
|
||||
<code className="block selectable">{preview || <span> </span>}</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -287,7 +325,7 @@ class TagEditor extends PureComponent {
|
||||
<select ref={this._setSelectRef}
|
||||
onChange={this._handleChangeTag}
|
||||
value={activeTagDefinition ? activeTagDefinition.name : ''}>
|
||||
{templating.getTagDefinitions().map((tagDefinition, i) => (
|
||||
{this.state.tagDefinitions.map((tagDefinition, i) => (
|
||||
<option key={`${i}::${tagDefinition.name}`} value={tagDefinition.name}>
|
||||
{tagDefinition.displayName} – {tagDefinition.description}
|
||||
</option>
|
||||
@ -308,13 +346,17 @@ class TagEditor extends PureComponent {
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Live Preview
|
||||
{error
|
||||
? <code className="block danger selectable">{error || <span> </span>}</code>
|
||||
: <code className="block selectable">{preview || <span> </span>}</code>
|
||||
}
|
||||
</label>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<button type="button"
|
||||
className="txt-sm pull-right icon inline-block"
|
||||
onClick={this._handleRefresh}>
|
||||
refresh <i className={classnames('fa fa-refresh', {'fa-spin': rendering})}/>
|
||||
</button>
|
||||
<label>Live Preview
|
||||
{previewElement}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -209,7 +209,7 @@ class App extends PureComponent {
|
||||
|
||||
_requestGroupCreate (parentId) {
|
||||
showPrompt({
|
||||
headerName: 'New Folder',
|
||||
title: 'New Folder',
|
||||
defaultValue: 'My Folder',
|
||||
submitName: 'Create',
|
||||
label: 'Name',
|
||||
@ -245,7 +245,7 @@ class App extends PureComponent {
|
||||
async _workspaceDuplicate (callback) {
|
||||
const workspace = this.props.activeWorkspace;
|
||||
showPrompt({
|
||||
headerName: 'Duplicate Workspace',
|
||||
title: 'Duplicate Workspace',
|
||||
defaultValue: `${workspace.name} (Copy)`,
|
||||
submitName: 'Duplicate',
|
||||
selectText: true,
|
||||
|
@ -142,8 +142,8 @@
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
margin-left: @padding-xxs;
|
||||
margin-right: @padding-xxs;
|
||||
}
|
||||
|
||||
& > p {
|
||||
|
57
flow-typed/node-libcurl.js
vendored
57
flow-typed/node-libcurl.js
vendored
@ -1,38 +1,39 @@
|
||||
declare class Curl {
|
||||
static option: {
|
||||
ACCEPT_ENCODING: string,
|
||||
CAINFO: string,
|
||||
COOKIE: string,
|
||||
COOKIEFILE: string,
|
||||
COOKIELIST: string,
|
||||
CUSTOMREQUEST: string,
|
||||
DEBUGFUNCTION: string,
|
||||
FOLLOWLOCATION: string,
|
||||
HTTPAUTH: string,
|
||||
PASSWORD: string,
|
||||
USERNAME: string,
|
||||
USERAGENT: string,
|
||||
POSTFIELDS: string,
|
||||
READDATA: string,
|
||||
UPLOAD: string,
|
||||
HTTPHEADER: string,
|
||||
HTTPPOST: string,
|
||||
INFILESIZE: string,
|
||||
KEYPASSWD: string,
|
||||
HTTPHEADER: string,
|
||||
SSLCERTTYPE: string,
|
||||
SSLCERT: string,
|
||||
SSLKEY: string,
|
||||
PROXY: string,
|
||||
NOPROXY: string,
|
||||
PROXYAUTH: string,
|
||||
COOKIELIST: string,
|
||||
COOKIEFILE: string,
|
||||
CAINFO: string,
|
||||
SSL_VERIFYPEER: string,
|
||||
SSL_VERIFYHOST: string,
|
||||
UNIX_SOCKET_PATH: string,
|
||||
URL: string,
|
||||
XFERINFOFUNCTION: string,
|
||||
DEBUGFUNCTION: string,
|
||||
ACCEPT_ENCODING: string,
|
||||
NOPROGRESS: string,
|
||||
VERBOSE: string,
|
||||
TIMEOUT_MS: string,
|
||||
FOLLOWLOCATION: string,
|
||||
NOBODY: string,
|
||||
CUSTOMREQUEST: string,
|
||||
NOPROGRESS: string,
|
||||
NOPROXY: string,
|
||||
PASSWORD: string,
|
||||
POSTFIELDS: string,
|
||||
PROXY: string,
|
||||
PROXYAUTH: string,
|
||||
READDATA: string,
|
||||
SSLCERT: string,
|
||||
SSLCERTTYPE: string,
|
||||
SSLKEY: string,
|
||||
SSL_VERIFYHOST: string,
|
||||
SSL_VERIFYPEER: string,
|
||||
TIMEOUT_MS: string,
|
||||
UNIX_SOCKET_PATH: string,
|
||||
UPLOAD: string,
|
||||
URL: string,
|
||||
USERAGENT: string,
|
||||
USERNAME: string,
|
||||
VERBOSE: string,
|
||||
XFERINFOFUNCTION: string,
|
||||
};
|
||||
|
||||
static auth: {
|
||||
|
3
flow-typed/react-flow-types.js
vendored
Normal file
3
flow-typed/react-flow-types.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare module 'react-flow-types' {
|
||||
declare module.exports: *
|
||||
}
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -4285,9 +4285,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"jest-runtime": {
|
||||
"version": "19.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-19.0.3.tgz",
|
||||
"integrity": "sha1-oWM1Ss5GkQ7jPwKCtr/2sLh9QzA=",
|
||||
"version": "19.0.4",
|
||||
"resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-19.0.4.tgz",
|
||||
"integrity": "sha1-8WfZ8TR3UvICc2EGeSZIU0n8wkU=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
@ -5991,9 +5991,9 @@
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.0.0.tgz",
|
||||
"integrity": "sha1-VATpOlRMT+x/BIJil3vr/jFV4ME=",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz",
|
||||
"integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user