From 2d3e4e4b3c09caa83458596a312dd73c8997b11f Mon Sep 17 00:00:00 2001 From: Jack Kavanagh Date: Wed, 16 Aug 2023 11:45:20 +0200 Subject: [PATCH] Paste from Curl (#6295) * test curl fill * clean up updatemimetype * clean up mime type setting * add from curl request type * remove paste handling * add request group dropdown button * fix test * comment out from curl test --- .../tests/smoke/app.test.ts | 11 ++ .../src/models/__tests__/request.test.ts | 49 +------- .../components/codemirror/one-line-editor.tsx | 8 -- .../dropdowns/content-type-dropdown.tsx | 111 ++++-------------- .../dropdowns/request-actions-dropdown.tsx | 1 - .../request-group-actions-dropdown.tsx | 30 +++-- .../src/ui/components/panes/request-pane.tsx | 4 +- .../src/ui/components/request-url-bar.tsx | 59 +--------- .../ui/components/settings/import-export.tsx | 1 - .../sidebar/sidebar-create-dropdown.tsx | 32 +++-- packages/insomnia/src/ui/hooks/use-request.ts | 2 +- packages/insomnia/src/ui/routes/request.tsx | 63 ++++++---- .../insomnia/src/utils/url/querystring.ts | 2 +- 13 files changed, 126 insertions(+), 247 deletions(-) diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts b/packages/insomnia-smoke-test/tests/smoke/app.test.ts index 908b2a7ce..d3565bcbe 100644 --- a/packages/insomnia-smoke-test/tests/smoke/app.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/app.test.ts @@ -21,6 +21,17 @@ test('can send requests', async ({ app, page }) => { await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); await page.getByText('CollectionSmoke testsjust now').click(); + // re enable test when sidebar selector is fixed + // await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click(); + // await page.getByRole('menuitem', { name: 'Http Request' }).click(); + // const curl = 'curl --request POST --url http://mockbin.org/status/200'; + // await app.evaluate(async ({ clipboard }, curl) => clipboard.writeText(curl), curl); + // await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click(); + // await page.getByRole('menuitem', { name: 'From Curl' }).click(); + + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + await expect(statusTag).toContainText('200 OK'); + await page.getByRole('button', { name: 'send JSON request' }).click(); await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); await expect(statusTag).toContainText('200 OK'); diff --git a/packages/insomnia/src/models/__tests__/request.test.ts b/packages/insomnia/src/models/__tests__/request.test.ts index fa70bc2ca..9e31407c8 100644 --- a/packages/insomnia/src/models/__tests__/request.test.ts +++ b/packages/insomnia/src/models/__tests__/request.test.ts @@ -1,6 +1,5 @@ import { beforeEach, describe, expect, it, jest } from '@jest/globals'; -import { version } from '../../../package.json'; import { globalBeforeEach } from '../../__jest__/before-each'; import { CONTENT_TYPE_GRAPHQL } from '../../common/constants'; import { newBodyGraphQL, updateMimeType } from '../../ui/components/dropdowns/content-type-dropdown'; @@ -93,9 +92,6 @@ describe('updateMimeType()', () => { { name: 'Content-Type', value: 'text/html', - }, { - 'name': 'User-Agent', - 'value': `Insomnia/${version}`, }, ]); }); @@ -123,7 +119,7 @@ describe('updateMimeType()', () => { const newRequest = await updateMimeType(request, 'text/html'); expect(newRequest.headers).toEqual([ { - name: 'content-tYPE', + name: 'Content-Type', value: 'text/html', }, { @@ -133,10 +129,7 @@ describe('updateMimeType()', () => { { bad: true, }, - null, { - 'name': 'User-Agent', - 'value': `Insomnia/${version}`, - }, + null, ]); }); @@ -155,11 +148,8 @@ describe('updateMimeType()', () => { const newRequest = await updateMimeType(request, 'text/html'); expect(newRequest.headers).toEqual([ { - name: 'content-tYPE', + name: 'Content-Type', value: 'text/html', - }, { - 'name': 'User-Agent', - 'value': `Insomnia/${version}`, }, ]); }); @@ -178,38 +168,7 @@ describe('updateMimeType()', () => { expect(request).not.toBeNull(); const newRequest = await updateMimeType(request, null); expect(newRequest.body).toEqual({}); - expect(newRequest.headers).toEqual([{ - 'name': 'User-Agent', - 'value': `Insomnia/${version}`, - }]); - }); - - it('uses saved body when provided', async () => { - const request = await models.request.create({ - name: 'My Request', - parentId: 'fld_1', - body: { - text: 'My Data', - }, - }); - expect(request).not.toBeNull(); - const newRequest = await updateMimeType(request, 'application/json', { - text: 'Saved Data', - }); - expect(newRequest.body.text).toEqual('Saved Data'); - }); - - it('uses existing body when saved body not provided', async () => { - const request = await models.request.create({ - name: 'My Request', - parentId: 'fld_1', - body: { - text: 'My Data', - }, - }); - expect(request).not.toBeNull(); - const newRequest = await updateMimeType(request, 'application/json', {}); - expect(newRequest.body.text).toEqual('My Data'); + expect(newRequest.headers).toEqual([]); }); }); diff --git a/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx b/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx index bf5a384b4..67d0491a3 100644 --- a/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx +++ b/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx @@ -21,7 +21,6 @@ export interface OneLineEditorProps { id: string; onChange: (value: string) => void; onKeyDown?: (event: KeyboardEvent, value: string) => void; - onPaste?: (event: ClipboardEvent) => void; placeholder?: string; readOnly?: boolean; type?: string; @@ -37,7 +36,6 @@ export const OneLineEditor = forwardRef id, onChange, onKeyDown, - onPaste, placeholder, readOnly, type, @@ -188,12 +186,6 @@ export const OneLineEditor = forwardRef return () => codeMirror.current?.off('changes', fn); }, [onChange]); - useEffect(() => { - const handlePaste = (_: CodeMirror.Editor, e: ClipboardEvent) => onPaste?.(e); - codeMirror.current?.on('paste', handlePaste); - return () => codeMirror.current?.on('paste', handlePaste); - }, [onPaste]); - useEffect(() => window.main.on('context-menu-command', (_, { key, tag }) => id === key && codeMirror.current?.replaceSelection(tag)), [id]); diff --git a/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx index dac9ffcb9..c22c365f1 100644 --- a/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx @@ -1,7 +1,6 @@ import React, { FC } from 'react'; import { useParams, useRouteLoaderData } from 'react-router-dom'; -import { version } from '../../../../package.json'; import { CONTENT_TYPE_EDN, CONTENT_TYPE_FILE, @@ -16,8 +15,7 @@ import { getContentTypeName, METHOD_POST, } from '../../../common/constants'; -import { getContentTypeHeader } from '../../../common/misc'; -import { Request, RequestBody } from '../../../models/request'; +import { Request, RequestBody, RequestHeader, RequestParameter } from '../../../models/request'; import { deconstructQueryStringToParams } from '../../../utils/url/querystring'; import { SegmentEvent } from '../../analytics'; import { useRequestPatcher } from '../../hooks/use-request'; @@ -217,104 +215,41 @@ export function newBodyGraphQL(rawBody: string): RequestBody { } } } + export const updateMimeType = ( request: Request, mimeType: string | null, - savedBody: RequestBody = {}, -) => { - let headers = request.headers ? [...request.headers] : []; - const contentTypeHeader = getContentTypeHeader(headers); - // GraphQL uses JSON content-type - const contentTypeHeaderValue = mimeType === CONTENT_TYPE_GRAPHQL ? CONTENT_TYPE_JSON : mimeType; - // GraphQL must be POST - if (mimeType === CONTENT_TYPE_GRAPHQL) { - request.method = METHOD_POST; - } - // Check if we are converting to/from variants of XML or JSON - let leaveContentTypeAlone = false; - if (contentTypeHeader && mimeType) { - const current = contentTypeHeader.value; - if (current.includes('xml') && mimeType.includes('xml')) { - leaveContentTypeAlone = true; - } else if (current.includes('json') && mimeType.includes('json')) { - leaveContentTypeAlone = true; - } - } - const hasBody = typeof mimeType === 'string'; - if (!hasBody) { - headers = headers.filter(h => h !== contentTypeHeader); - } else if (mimeType === CONTENT_TYPE_OTHER) { - // Leave headers alone - } else if (mimeType && contentTypeHeader && !leaveContentTypeAlone) { - contentTypeHeader.value = contentTypeHeaderValue || ''; - } else if (mimeType && !contentTypeHeader) { - headers.push({ - name: 'Content-Type', - value: contentTypeHeaderValue || '', - }); - } - if (!headers.find(h => h?.name?.toLowerCase() === 'user-agent')) { - headers.push({ - name: 'User-Agent', - value: `Insomnia/${version}`, - }); - } - const oldBody = Object.keys(savedBody).length === 0 ? request.body : savedBody; - if (mimeType === CONTENT_TYPE_FORM_URLENCODED) { +): { body: RequestBody; headers: RequestHeader[]; params?: RequestParameter[]; method?: string } => { + const withoutContentType = request.headers.filter(h => h?.name?.toLowerCase() !== 'content-type'); + // 'No body' selected + if (typeof mimeType !== 'string') { return { - body: { - mimeType: CONTENT_TYPE_FORM_URLENCODED, - params: oldBody.params || (oldBody.text ? deconstructQueryStringToParams(oldBody.text) : []), - }, - headers, + body: {}, + headers: withoutContentType, }; } - if (mimeType === CONTENT_TYPE_FORM_DATA) { - // Form Data + if (mimeType === CONTENT_TYPE_GRAPHQL) { return { - body: oldBody.params - ? { - mimeType: CONTENT_TYPE_FORM_DATA, - params: oldBody.params || [], - } : { - mimeType: CONTENT_TYPE_FORM_DATA, - params: oldBody.text ? deconstructQueryStringToParams(oldBody.text) : [], - }, - headers, + body: newBodyGraphQL(request.body.text || ''), + headers: [{ name: 'Content-Type', value: CONTENT_TYPE_JSON }, ...withoutContentType], + method: METHOD_POST, + }; + } + if (mimeType === CONTENT_TYPE_FORM_URLENCODED || mimeType === CONTENT_TYPE_FORM_DATA) { + const params = request.body.params || deconstructQueryStringToParams(request.body.text); + return { + body: { mimeType, params }, + headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType], }; } if (mimeType === CONTENT_TYPE_FILE) { return { - body: { - mimeType: CONTENT_TYPE_FILE, - fileName: '', - }, - headers, + body: { mimeType, fileName: '' }, + headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType], }; } - if (mimeType === CONTENT_TYPE_GRAPHQL) { - if (contentTypeHeader) { - contentTypeHeader.value = CONTENT_TYPE_JSON; - } - return { - body: newBodyGraphQL(oldBody.text || ''), - headers, - }; - } - if (typeof mimeType !== 'string') { - return { - body: {}, - headers, - }; - } - // Raw Content-Type (ex: application/json) return { - body: typeof mimeType !== 'string' ? { - text: oldBody.text || '', - } : { - mimeType: mimeType.split(';')[0], - text: oldBody.text || '', - }, - headers, + body: { mimeType: mimeType.split(';')[0], text: request.body.text || '' }, + headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType], }; }; diff --git a/packages/insomnia/src/ui/components/dropdowns/request-actions-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/request-actions-dropdown.tsx index df6f71809..38186435c 100644 --- a/packages/insomnia/src/ui/components/dropdowns/request-actions-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/request-actions-dropdown.tsx @@ -184,7 +184,6 @@ export const RequestActionsDropdown = forwardRef(({ icon="copy" label="Copy as cURL" onClick={copyAsCurl} - withPrompt /> )} diff --git a/packages/insomnia/src/ui/components/dropdowns/request-group-actions-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/request-group-actions-dropdown.tsx index 6c9f9e9ec..bbb23427a 100644 --- a/packages/insomnia/src/ui/components/dropdowns/request-group-actions-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/request-group-actions-dropdown.tsx @@ -43,7 +43,7 @@ export const RequestGroupActionsDropdown = forwardRef - requestFetcher.submit({ requestType, parentId: requestGroup._id }, + requestFetcher.submit({ requestType, parentId: requestGroup._id, clipboardText: window.clipboard.readText() }, { encType: 'application/json', action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`, @@ -202,15 +202,25 @@ export const RequestGroupActionsDropdown = forwardRef create('WebSocket')} /> - - - - + + + create('From Curl')} + /> + + + + + + + diff --git a/packages/insomnia/src/ui/components/panes/request-pane.tsx b/packages/insomnia/src/ui/components/panes/request-pane.tsx index dae8223d7..0cd14287f 100644 --- a/packages/insomnia/src/ui/components/panes/request-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/request-pane.tsx @@ -8,7 +8,7 @@ import * as models from '../../../models'; import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls'; import type { Settings } from '../../../models/settings'; import { deconstructQueryStringToParams, extractQueryStringFromUrl } from '../../../utils/url/querystring'; -import { useRequestPatcher, useSettingsPatcher } from '../../hooks/use-request'; +import { useSettingsPatcher } from '../../hooks/use-request'; import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version'; import { RequestLoaderData } from '../../routes/request'; import { WorkspaceLoaderData } from '../../routes/workspace'; @@ -67,7 +67,6 @@ export const RequestPane: FC = ({ }) => { const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; const { workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string }; - const patchRequest = useRequestPatcher(); const patchSettings = useSettingsPatcher(); const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] = useState(false); @@ -131,7 +130,6 @@ export const RequestPane: FC = ({ key={requestId} ref={requestUrlBarRef} uniquenessKey={uniqueKey} - onUrlChange={url => patchRequest(requestId, { url })} handleAutocompleteUrls={() => queryAllWorkspaceUrls(workspaceId, models.request.type, requestId)} nunjucksPowerUserMode={settings.nunjucksPowerUserMode} setLoading={setLoading} diff --git a/packages/insomnia/src/ui/components/request-url-bar.tsx b/packages/insomnia/src/ui/components/request-url-bar.tsx index 3e764d2a3..bfb7f3382 100644 --- a/packages/insomnia/src/ui/components/request-url-bar.tsx +++ b/packages/insomnia/src/ui/components/request-url-bar.tsx @@ -4,12 +4,10 @@ import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom'; import { useInterval } from 'react-use'; import styled from 'styled-components'; -import { database } from '../../common/database'; import { RENDER_PURPOSE_SEND } from '../../common/render'; import * as models from '../../models'; -import { isEventStreamRequest, isRequest } from '../../models/request'; +import { isEventStreamRequest } from '../../models/request'; import { fetchRequestData, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../../network/network'; -import { convert } from '../../utils/importers/convert'; import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../utils/try-interpolate'; import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/url/querystring'; import { SegmentEvent } from '../analytics'; @@ -40,7 +38,6 @@ const StyledDropdownButton = styled(DropdownButton)({ interface Props { handleAutocompleteUrls: () => Promise; nunjucksPowerUserMode: boolean; - onUrlChange: (url: string) => void; uniquenessKey: string; setLoading: (l: boolean) => void; } @@ -51,7 +48,6 @@ export interface RequestUrlBarHandle { export const RequestUrlBar = forwardRef(({ handleAutocompleteUrls, - onUrlChange, uniquenessKey, setLoading, }, ref) => { @@ -194,56 +190,6 @@ export const RequestUrlBar = forwardRef(({ }, }); - const lastPastedTextRef = useRef(''); - const handleImport = useCallback(async (text: string) => { - // Allow user to paste any import file into the url. If it results in - // only one item, it will overwrite the current request. - try { - const { data } = await convert(text); - const { resources } = data; - const r = resources[0]; - if (r && r._type === 'request' && activeRequest && isRequest(activeRequest)) { - // Only pull fields that we want to update - return database.update({ - ...activeRequest, - modified: Date.now(), - url: r.url, - method: r.method, - headers: r.headers, - body: r.body, - authentication: r.authentication, - parameters: r.parameters, - }, true); // Pass true to indicate that this is an import - } - } catch (error) { - // Import failed, that's alright - console.error(error); - } - return null; - }, [activeRequest]); - - const handleUrlChange = useCallback(async (url: string) => { - const pastedText = lastPastedTextRef.current; - // If no pasted text in the queue, just fire the regular change handler - if (!pastedText) { - onUrlChange(url); - return; - } - // Reset pasted text cache - lastPastedTextRef.current = ''; - // Attempt to import the pasted text - const importedRequest = await handleImport(pastedText); - // Update depending on whether something was imported - if (!importedRequest) { - onUrlChange(url); - } - }, [handleImport, onUrlChange]); - - const handleUrlPaste = useCallback((event: ClipboardEvent) => { - // NOTE: We're not actually doing the import here to avoid races with onChange - lastPastedTextRef.current = event.clipboardData?.getData('text/plain') || ''; - }, []); - const handleSendDropdownHide = useCallback(() => { buttonRef.current?.blur(); }, []); @@ -263,12 +209,11 @@ export const RequestUrlBar = forwardRef(({ id="request-url-bar" key={uniquenessKey} ref={inputRef} - onPaste={handleUrlPaste} type="text" getAutocompleteConstants={handleAutocompleteUrls} placeholder="https://api.myproduct.com/v1/users" defaultValue={url} - onChange={handleUrlChange} + onChange={url => patchRequest(requestId, { url })} onKeyDown={createKeybindingsHandler({ 'Enter': () => sendOrConnect(), })} diff --git a/packages/insomnia/src/ui/components/settings/import-export.tsx b/packages/insomnia/src/ui/components/settings/import-export.tsx index 08423ef56..e925986e0 100644 --- a/packages/insomnia/src/ui/components/settings/import-export.tsx +++ b/packages/insomnia/src/ui/components/settings/import-export.tsx @@ -103,7 +103,6 @@ export const ImportExport: FC = ({ hideSettingsModal }) => { Create Run Button -

* Tip: You can also paste Curl commands into the URL bar

{isImportModalOpen && ( { @@ -15,7 +15,7 @@ export const SidebarCreateDropdown = () => { const requestFetcher = useFetcher(); const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string }; const create = useCallback((requestType: CreateRequestType) => - requestFetcher.submit({ requestType, parentId: workspaceId }, + requestFetcher.submit({ requestType, parentId: workspaceId, clipboardText: window.clipboard.readText() }, { action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`, method: 'post', @@ -91,15 +91,25 @@ export const SidebarCreateDropdown = () => { onClick={() => create('WebSocket')} />
- - - - + + + create('From Curl')} + /> + + + + + + + ); }; diff --git a/packages/insomnia/src/ui/hooks/use-request.ts b/packages/insomnia/src/ui/hooks/use-request.ts index c66809449..ae49b9a5c 100644 --- a/packages/insomnia/src/ui/hooks/use-request.ts +++ b/packages/insomnia/src/ui/hooks/use-request.ts @@ -82,4 +82,4 @@ export const useWorkspaceMetaPatcher = () => { }; }; -export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream'; +export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream' | 'From Curl'; diff --git a/packages/insomnia/src/ui/routes/request.tsx b/packages/insomnia/src/ui/routes/request.tsx index 7136328ef..7fec08b20 100644 --- a/packages/insomnia/src/ui/routes/request.tsx +++ b/packages/insomnia/src/ui/routes/request.tsx @@ -5,6 +5,7 @@ import * as contentDisposition from 'content-disposition'; import { extension as mimeExtension } from 'mime-types'; import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom'; +import { version } from '../../../package.json'; import { CONTENT_TYPE_EVENT_STREAM, CONTENT_TYPE_GRAPHQL, CONTENT_TYPE_JSON, METHOD_GET, METHOD_POST } from '../../common/constants'; import { ChangeBufferEvent, database } from '../../common/database'; import { getContentDispositionHeader } from '../../common/misc'; @@ -16,16 +17,18 @@ import { CookieJar } from '../../models/cookie-jar'; import { GrpcRequest, isGrpcRequestId } from '../../models/grpc-request'; import { GrpcRequestMeta } from '../../models/grpc-request-meta'; import * as requestOperations from '../../models/helpers/request-operations'; -import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestHeader } from '../../models/request'; +import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestBody, RequestHeader, RequestParameter } from '../../models/request'; import { isRequestMeta, RequestMeta } from '../../models/request-meta'; import { RequestVersion } from '../../models/request-version'; import { Response } from '../../models/response'; import { isWebSocketRequestId, WebSocketRequest } from '../../models/websocket-request'; import { WebSocketResponse } from '../../models/websocket-response'; import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest } from '../../network/network'; +import { convert } from '../../utils/importers/convert'; import { invariant } from '../../utils/invariant'; import { SegmentEvent } from '../analytics'; import { updateMimeType } from '../components/dropdowns/content-type-dropdown'; +import { CreateRequestType } from '../hooks/use-request'; export interface WebSocketRequestLoaderData { activeRequest: WebSocketRequest; @@ -93,7 +96,7 @@ export const loader: LoaderFunction = async ({ params }): Promise { const { organizationId, projectId, workspaceId } = params; invariant(typeof workspaceId === 'string', 'Workspace ID is required'); - const { requestType, parentId } = await request.json(); + const { requestType, parentId, clipboardText } = await request.json() as { requestType: CreateRequestType; parentId?: string; clipboardText?: string }; let activeRequestId; if (requestType === 'HTTP') { @@ -101,6 +104,7 @@ export const createRequestAction: ActionFunction = async ({ request, params }) = parentId: parentId || workspaceId, method: METHOD_GET, name: 'New Request', + headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }], }))._id; } if (requestType === 'gRPC') { @@ -114,10 +118,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) = parentId: parentId || workspaceId, method: METHOD_POST, headers: [ - { - name: 'Content-Type', - value: CONTENT_TYPE_JSON, - }, + { name: 'User-Agent', value: `Insomnia/${version}` }, + { name: 'Content-Type', value: CONTENT_TYPE_JSON }, ], body: { mimeType: CONTENT_TYPE_GRAPHQL, @@ -132,10 +134,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) = method: METHOD_GET, url: '', headers: [ - { - name: 'Accept', - value: CONTENT_TYPE_EVENT_STREAM, - }, + { name: 'User-Agent', value: `Insomnia/${version}` }, + { name: 'Accept', value: CONTENT_TYPE_EVENT_STREAM }, ], name: 'New Event Stream', }))._id; @@ -144,8 +144,33 @@ export const createRequestAction: ActionFunction = async ({ request, params }) = activeRequestId = (await models.webSocketRequest.create({ parentId: parentId || workspaceId, name: 'New WebSocket Request', + headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }], }))._id; } + if (requestType === 'From Curl') { + // TODO: if no clipboard text show modal + if (!clipboardText) { + return null; + } + try { + const { data } = await convert(clipboardText); + const { resources } = data; + const r = resources[0]; + + activeRequestId = (await models.request.create({ + parentId: parentId || workspaceId, + url: r.url, + method: r.method, + headers: r.headers, + body: r.body as RequestBody, + authentication: r.authentication, + parameters: r.parameters as RequestParameter[], + }))._id; + } catch (error) { + console.error(error); + return null; + } + } invariant(typeof activeRequestId === 'string', 'Request ID is required'); models.stats.incrementCreatedRequests(); window.main.trackSegmentEvent({ event: SegmentEvent.requestCreate, properties: { requestType } }); @@ -155,21 +180,17 @@ export const createRequestAction: ActionFunction = async ({ request, params }) = export const updateRequestAction: ActionFunction = async ({ request, params }) => { const { requestId } = params; invariant(typeof requestId === 'string', 'Request ID is required'); - let req = await requestOperations.getById(requestId); + const req = await requestOperations.getById(requestId); invariant(req, 'Request not found'); - let patch = await request.json(); + const patch = await request.json(); // TODO: if gRPC, we should also copy the protofile to the destination workspace - INS-267 - if (isRequest(req) && patch.body) { - const mimeType = patch.body?.mimeType as string | null; - const requestMeta = await models.requestMeta.getOrCreateByParentId(requestId); - const savedRequestBody = !mimeType ? (req.body || {}) : {}; - await models.requestMeta.update(requestMeta, { savedRequestBody }); - // TODO: make this less hacky, update expects latest req not patch - req = await requestOperations.update(req, patch); - patch = updateMimeType(req, mimeType, requestMeta.savedRequestBody); + const isMimeTypeChanged = isRequest(req) && patch.body && patch.body.mimeType !== req.body.mimeType; + if (isMimeTypeChanged) { + await requestOperations.update(req, { ...patch, ...updateMimeType(req, patch.body?.mimeType) }); + return null; } - requestOperations.update(req, patch); + await requestOperations.update(req, patch); return null; }; diff --git a/packages/insomnia/src/utils/url/querystring.ts b/packages/insomnia/src/utils/url/querystring.ts index d7bd06243..1121977df 100644 --- a/packages/insomnia/src/utils/url/querystring.ts +++ b/packages/insomnia/src/utils/url/querystring.ts @@ -113,7 +113,7 @@ export const buildQueryStringFromParams = ( * @returns {{name: string, value: string}[]} */ export const deconstructQueryStringToParams = ( - qs: string, + qs?: string, /** allow empty names and values */ strict?: boolean,