diff --git a/app/common/misc.js b/app/common/misc.js index 894c23334a..dfd5864b67 100644 --- a/app/common/misc.js +++ b/app/common/misc.js @@ -1,3 +1,4 @@ +// @flow import uuid from 'uuid'; import zlib from 'zlib'; import {format as urlFormat, parse as urlParse} from 'url'; @@ -7,7 +8,12 @@ import {shell} from 'electron'; const URL_PATH_CHARACTER_WHITELIST = '+,;@=:'; -export function getBasicAuthHeader (username, password) { +type Header = { + name: string, + value: string +}; + +export function getBasicAuthHeader (username: ?string, password: ?string): Header { const name = 'Authorization'; const header = `${username || ''}:${password || ''}`; const authString = Buffer.from(header, 'utf8').toString('base64'); @@ -15,13 +21,13 @@ export function getBasicAuthHeader (username, password) { return {name, value}; } -export function getBearerAuthHeader (token) { +export function getBearerAuthHeader (token: string): Header { const name = 'Authorization'; const value = `Bearer ${token}`; return {name, value}; } -export function filterHeaders (headers, name) { +export function filterHeaders (headers: Array, name: string): Array { if (!Array.isArray(headers) || !name) { return []; } @@ -35,41 +41,41 @@ export function filterHeaders (headers, name) { }); } -export function hasContentTypeHeader (headers) { +export function hasContentTypeHeader (headers: Array): boolean { return filterHeaders(headers, 'content-type').length > 0; } -export function hasContentLengthHeader (headers) { +export function hasContentLengthHeader (headers: Array): boolean { return filterHeaders(headers, 'content-length').length > 0; } -export function hasAuthHeader (headers) { +export function hasAuthHeader (headers: Array): boolean { return filterHeaders(headers, 'authorization').length > 0; } -export function hasAcceptHeader (headers) { +export function hasAcceptHeader (headers: Array): boolean { return filterHeaders(headers, 'accept').length > 0; } -export function hasUserAgentHeader (headers) { +export function hasUserAgentHeader (headers: Array): boolean { return filterHeaders(headers, 'user-agent').length > 0; } -export function getSetCookieHeaders (headers) { +export function getSetCookieHeaders (headers: Array): Array { return filterHeaders(headers, 'set-cookie'); } -export function getContentTypeHeader (headers) { +export function getContentTypeHeader (headers: Array): T | null { const matches = filterHeaders(headers, 'content-type'); return matches.length ? matches[0] : null; } -export function getContentLengthHeader (headers) { +export function getContentLengthHeader (headers: Array): T | null { const matches = filterHeaders(headers, 'content-length'); return matches.length ? matches[0] : null; } -export function setDefaultProtocol (url, defaultProto = 'http:') { +export function setDefaultProtocol (url: string, defaultProto: string = 'http:'): string { // If no url, don't bother returning anything if (!url) { return ''; @@ -88,7 +94,7 @@ export function setDefaultProtocol (url, defaultProto = 'http:') { * @param prefix * @returns {string} */ -export function generateId (prefix) { +export function generateId (prefix: string): string { const id = uuid.v4().replace(/-/g, ''); if (prefix) { @@ -98,7 +104,7 @@ export function generateId (prefix) { } } -export function flexibleEncodeComponent (str, ignore = '') { +export function flexibleEncodeComponent (str: string, ignore: string = ''): string { // Sometimes spaces screw things up because of url.parse str = str.replace(/%20/g, ' '); @@ -146,7 +152,7 @@ export function flexibleEncodeComponent (str, ignore = '') { return str; } -export function prepareUrlForSending (url, autoEncode = true) { +export function prepareUrlForSending (url: string, autoEncode: boolean = true): string { const urlWithProto = setDefaultProtocol(url); if (!autoEncode) { @@ -188,15 +194,15 @@ export function prepareUrlForSending (url, autoEncode = true) { } } -export function delay (milliseconds = DEBOUNCE_MILLIS) { +export function delay (milliseconds: number = DEBOUNCE_MILLIS): Promise { return new Promise(resolve => setTimeout(resolve, milliseconds)); } -export function removeVowels (str) { +export function removeVowels (str: string): string { return str.replace(/[aeiouyAEIOUY]/g, ''); } -export function keyedDebounce (callback, millis = DEBOUNCE_MILLIS) { +export function keyedDebounce (callback: Function, millis: number = DEBOUNCE_MILLIS): Function { let timeout = null; let results = {}; @@ -215,14 +221,14 @@ export function keyedDebounce (callback, millis = DEBOUNCE_MILLIS) { }; } -export function debounce (callback, millis = DEBOUNCE_MILLIS) { +export function debounce (callback: Function, millis: number = DEBOUNCE_MILLIS): Function { // For regular debounce, just use a keyed debounce with a fixed key return keyedDebounce(results => { callback.apply(null, results['__key__']); }, millis).bind(null, '__key__'); } -export function describeByteSize (bytes, long) { +export function describeByteSize (bytes: number, long: boolean = false): string { bytes = Math.round(bytes * 10) / 10; let size; @@ -248,19 +254,15 @@ export function describeByteSize (bytes, long) { return `${rounded} ${unit}`; } -export function nullFn () { +export function nullFn (): void { // Do nothing } -export function preventDefault (e) { +export function preventDefault (e: Event): void { e.preventDefault(); } -export function stopPropagation (e) { - e.stopPropagation(); -} - -export function clickLink (href) { +export function clickLink (href: string): void { if (href.match(/^http/i)) { const appName = isDevelopment() ? 'Insomnia Dev' : 'Insomnia'; const qs = `utm_source=${appName}&utm_medium=app&utm_campaign=v${getAppVersion()}`; @@ -272,7 +274,7 @@ export function clickLink (href) { } } -export function fnOrString (v, ...args) { +export function fnOrString (v: string | Function, ...args: Array) { if (typeof v === 'string') { return v; } else { @@ -280,20 +282,20 @@ export function fnOrString (v, ...args) { } } -export function compressObject (obj) { +export function compressObject (obj: any): string { const compressed = compress(JSON.stringify(obj)); return compressed.toString('base64'); } -export function decompressObject (input) { +export function decompressObject (input: string): any { const jsonBuffer = decompress(Buffer.from(input, 'base64')); - return JSON.parse(jsonBuffer); + return JSON.parse(jsonBuffer.toString('utf8')); } -export function compress (inputBuffer) { +export function compress (inputBuffer: Buffer | string): Buffer { return zlib.gzipSync(inputBuffer); } -export function decompress (inputBuffer) { +export function decompress (inputBuffer: Buffer | string): Buffer { return zlib.gunzipSync(inputBuffer); } diff --git a/app/models/response.js b/app/models/response.js index 083edfb493..21e3138bfb 100644 --- a/app/models/response.js +++ b/app/models/response.js @@ -144,7 +144,7 @@ export function storeBodyBuffer (bodyBuffer: Buffer | null) { const fullPath = path.join(dir, `${hash}.zip`); try { - fs.writeFileSync(fullPath, compress(bodyBuffer)); + fs.writeFileSync(fullPath, compress(bodyBuffer || Buffer.from(''))); } catch (err) { console.warn('Failed to write response body to file', err.message); } diff --git a/app/network/network.js b/app/network/network.js index e7afb4c116..5da5d798ee 100644 --- a/app/network/network.js +++ b/app/network/network.js @@ -1,7 +1,7 @@ // @flow import type {ResponseTimelineEntry} from '../models/response'; import type {BaseModel} from '../models/index'; -import type {Request} from '../models/request'; +import type {Request, RequestHeader} from '../models/request'; import type {Workspace} from '../models/workspace'; import electron from 'electron'; @@ -37,12 +37,6 @@ type CookieJar = { cookies: Array } -type Header = { - name: string, - value: string, - disabled: boolean -} - type RenderedRequest = BaseModel & Request & { cookies: Array<{name: string, value: string, disabled: boolean}>, cookieJar: CookieJar @@ -616,6 +610,7 @@ export async function send (requestId: string, environmentId: string) { throw new Error(`Failed to find workspace for request: ${requestId}`); } + // Render succeeded so we're good to go! return _actuallySend(renderedRequest, workspace, settings); } @@ -636,7 +631,7 @@ function _getCurlHeader (curlHeadersObj: {[string]: string}, name: string, fallb export function _getAwsAuthHeaders ( accessKeyId: string, secretAccessKey: string, - headers: Array
, + headers: Array, body: string, url: string ) { diff --git a/app/ui/components/request-pane.js b/app/ui/components/request-pane.js index ffe441ff68..d8f4095078 100644 --- a/app/ui/components/request-pane.js +++ b/app/ui/components/request-pane.js @@ -2,7 +2,7 @@ import type {Request} from '../../models/request'; import type {Workspace} from '../../models/workspace'; -import React, {PropTypes, PureComponent} from 'react'; +import React from 'react'; import autobind from 'autobind-decorator'; import {Tab, TabList, TabPanel, Tabs} from 'react-tabs'; import ContentTypeDropdown from './dropdowns/content-type-dropdown'; @@ -13,8 +13,7 @@ import RenderedQueryString from './rendered-query-string'; import BodyEditor from './editors/body/body-editor'; import AuthWrapper from './editors/auth/auth-wrapper'; import RequestUrlBar from './request-url-bar.js'; -import {getAuthTypeName, getContentTypeName} from '../../common/constants'; -import {debounce} from '../../common/misc'; +import {DEBOUNCE_MILLIS, getAuthTypeName, getContentTypeName} from '../../common/constants'; import {trackEvent} from '../../analytics/index'; import * as querystring from '../../common/querystring'; import * as db from '../../common/database'; @@ -61,13 +60,10 @@ type Props = { } @autobind -class RequestPane extends PureComponent { - _handleUpdateRequestUrl: Function; +class RequestPane extends React.PureComponent { + props: Props; - constructor (props: Props) { - super(props); - this._handleUpdateRequestUrl = debounce(this._handleUpdateRequestUrl); - } + _handleUpdateRequestUrlTimeout: number; _handleEditDescriptionAdd () { this._handleEditDescription(true); @@ -114,7 +110,10 @@ class RequestPane extends PureComponent { } _handleUpdateRequestUrl (url: string) { - this.props.updateRequestUrl(url); + clearTimeout(this._handleUpdateRequestUrlTimeout); + this._handleUpdateRequestUrlTimeout = setTimeout(() => { + this.props.updateRequestUrl(url); + }, DEBOUNCE_MILLIS); } _handleImportQueryFromUrl () { @@ -440,40 +439,4 @@ class RequestPane extends PureComponent { } } -RequestPane.propTypes = { - // Functions - forceUpdateRequest: PropTypes.func.isRequired, - handleSend: PropTypes.func.isRequired, - handleSendAndDownload: PropTypes.func.isRequired, - handleCreateRequest: PropTypes.func.isRequired, - handleGenerateCode: PropTypes.func.isRequired, - handleRender: PropTypes.func.isRequired, - handleGetRenderContext: PropTypes.func.isRequired, - updateRequestUrl: PropTypes.func.isRequired, - updateRequestMethod: PropTypes.func.isRequired, - updateRequestBody: PropTypes.func.isRequired, - updateRequestParameters: PropTypes.func.isRequired, - updateRequestAuthentication: PropTypes.func.isRequired, - updateRequestHeaders: PropTypes.func.isRequired, - updateRequestMimeType: PropTypes.func.isRequired, - updateSettingsShowPasswords: PropTypes.func.isRequired, - updateSettingsUseBulkHeaderEditor: PropTypes.func.isRequired, - handleImport: PropTypes.func.isRequired, - handleImportFile: PropTypes.func.isRequired, - - // Other - useBulkHeaderEditor: PropTypes.bool.isRequired, - showPasswords: PropTypes.bool.isRequired, - editorFontSize: PropTypes.number.isRequired, - editorIndentSize: PropTypes.number.isRequired, - editorKeyMap: PropTypes.string.isRequired, - editorLineWrapping: PropTypes.bool.isRequired, - workspace: PropTypes.object.isRequired, - forceRefreshCounter: PropTypes.number.isRequired, - - // Optional - request: PropTypes.object, - oAuth2Token: PropTypes.object -}; - export default RequestPane; diff --git a/flow-typed/aws4.js b/flow-typed/aws4.js index 9eecb960a6..ce2c899dcf 100644 --- a/flow-typed/aws4.js +++ b/flow-typed/aws4.js @@ -1,3 +1,5 @@ declare module 'aws4' { - declare module.exports: * + declare module.exports: { + sign: (options: Object, credentials: Object) => {headers: {[string]: string}} + } } diff --git a/flow-typed/clone.js b/flow-typed/clone.js index 12e47089de..d3e3797521 100644 --- a/flow-typed/clone.js +++ b/flow-typed/clone.js @@ -1,3 +1,3 @@ declare module 'clone' { - declare module.exports: (value: *) => * + declare module.exports: (value: T) => T } diff --git a/flow-typed/electron.js b/flow-typed/electron.js index 7515628a01..eb25b69e8a 100644 --- a/flow-typed/electron.js +++ b/flow-typed/electron.js @@ -1,5 +1,6 @@ declare module 'electron' { declare module.exports: { - remote: * + shell: Object, + remote: Object } } diff --git a/flow-typed/mime-types.js b/flow-typed/mime-types.js index 864ffd78c7..45c56670c0 100644 --- a/flow-typed/mime-types.js +++ b/flow-typed/mime-types.js @@ -1,3 +1,6 @@ declare module 'mime-types' { - declare module.exports: * + declare module.exports: { + lookup: (fileName: string) => string, + extension: (mimeType: string) => string + } } diff --git a/flow-typed/mkdirp.js b/flow-typed/mkdirp.js index 59ee7639f7..5a9cc168f9 100644 --- a/flow-typed/mkdirp.js +++ b/flow-typed/mkdirp.js @@ -1,3 +1,5 @@ declare module 'mkdirp' { - declare module.exports: * + declare module.exports: { + sync: (path: string) => void + } } diff --git a/flow-typed/node-libcurl.js b/flow-typed/node-libcurl.js index 3f8281b6d3..1fc29c28f1 100644 --- a/flow-typed/node-libcurl.js +++ b/flow-typed/node-libcurl.js @@ -1,3 +1,70 @@ -declare module 'node-libcurl' { - declare module.exports: * +declare class Curl { + static option: { + HTTPAUTH: string, + PASSWORD: string, + USERNAME: string, + USERAGENT: string, + POSTFIELDS: string, + READDATA: string, + UPLOAD: 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, + }; + + static auth: { + NTLM: string, + DIGEST: string, + BASIC: string, + ANY: string + }; + + static code: { + CURLE_ABORTED_BY_CALLBACK: string + }; + + static info: { + EFFECTIVE_URL: string, + SIZE_DOWNLOAD: string, + TOTAL_TIME: string, + debug: { + SSL_DATA_IN: string, + SSL_DATA_OUT: string, + } + }; + + setOpt: (option: string, ...args: Array) => void; + getInfo: (option: string, ...args: Array) => any; + perform: () => void; + close: () => void; + on: (event: string, callback: Function) => void; +} + +declare module 'node-libcurl' { + declare module.exports: { + Curl: typeof Curl + } } diff --git a/flow-typed/uuid.js b/flow-typed/uuid.js new file mode 100644 index 0000000000..f93ca072b7 --- /dev/null +++ b/flow-typed/uuid.js @@ -0,0 +1,5 @@ +declare module 'uuid' { + declare module.exports: { + v4: () => string + } +} diff --git a/package.json b/package.json index b8bc519680..1174293f81 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test:coverage": "cross-env NODE_ENV=test jest --coverage --silent && open ./coverage/lcov-report/index.html", "test:watch": "cross-env NODE_ENV=test jest --silent --watch", "test": "npm run test:lint && cross-env NODE_ENV=test jest --silent", + "flow": "flow", "start-hot": "npm run build-main && cross-env HOT=1 INSOMNIA_ENV=development electron app", "build-main": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.config.electron.babel.js", "hot-server": "webpack-dev-server --config ./webpack/webpack.config.development.babel.js",