mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 14:49:53 +00:00
squishy squash connect and send (#5204)
Co-authored-by: jackkav <jackkav@gmail.com>
This commit is contained in:
parent
883753a7df
commit
07833abcb5
@ -91,17 +91,18 @@ const parseResponseAndBuildTimeline = (url: string, incomingMessage: IncomingMes
|
|||||||
];
|
];
|
||||||
return { timeline, responseHeaders, statusCode, statusMessage, httpVersion };
|
return { timeline, responseHeaders, statusCode, statusMessage, httpVersion };
|
||||||
};
|
};
|
||||||
|
interface OpenWebSocketRequestOptions {
|
||||||
const createWebSocketConnection = async (
|
requestId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
url: string;
|
||||||
|
headers: RequestHeader[];
|
||||||
|
authentication: RequestAuthentication;
|
||||||
|
cookieJar: CookieJar;
|
||||||
|
initialPayload?: string;
|
||||||
|
}
|
||||||
|
const openWebSocketConnection = async (
|
||||||
event: Electron.IpcMainInvokeEvent,
|
event: Electron.IpcMainInvokeEvent,
|
||||||
options: {
|
options: OpenWebSocketRequestOptions
|
||||||
requestId: string;
|
|
||||||
workspaceId: string;
|
|
||||||
url: string;
|
|
||||||
headers: RequestHeader[];
|
|
||||||
authentication: RequestAuthentication;
|
|
||||||
cookieJar: CookieJar;
|
|
||||||
}
|
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const existingConnection = WebSocketConnections.get(options.requestId);
|
const existingConnection = WebSocketConnections.get(options.requestId);
|
||||||
|
|
||||||
@ -282,6 +283,10 @@ const createWebSocketConnection = async (
|
|||||||
eventLogFileStreams.get(options.requestId)?.write(JSON.stringify(openEvent) + '\n');
|
eventLogFileStreams.get(options.requestId)?.write(JSON.stringify(openEvent) + '\n');
|
||||||
timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: 'WebSocket connection established', name: 'Text', timestamp: Date.now() }) + '\n');
|
timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: 'WebSocket connection established', name: 'Text', timestamp: Date.now() }) + '\n');
|
||||||
event.sender.send(readyStateChannel, ws.readyState);
|
event.sender.send(readyStateChannel, ws.readyState);
|
||||||
|
|
||||||
|
if (options.initialPayload) {
|
||||||
|
sendPayload(ws, { requestId: options.requestId, payload: options.initialPayload });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener('message', ({ data }: MessageEvent) => {
|
ws.addEventListener('message', ({ data }: MessageEvent) => {
|
||||||
@ -369,17 +374,8 @@ const getWebSocketReadyState = async (
|
|||||||
return WebSocketConnections.get(options.requestId)?.readyState ?? 0;
|
return WebSocketConnections.get(options.requestId)?.readyState ?? 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendWebSocketEvent = async (
|
const sendPayload = async (ws: WebSocket, options: { payload: string; requestId: string }): Promise<void> => {
|
||||||
options: { message: string; requestId: string }
|
ws.send(options.payload, error => {
|
||||||
): Promise<void> => {
|
|
||||||
const ws = WebSocketConnections.get(options.requestId);
|
|
||||||
|
|
||||||
if (!ws) {
|
|
||||||
console.warn('No websocket found for requestId: ' + options.requestId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.send(options.message, error => {
|
|
||||||
// @TODO: We might want to set a status in the WebSocketMessageEvent
|
// @TODO: We might want to set a status in the WebSocketMessageEvent
|
||||||
// and update it here based on the error. e.g. status = 'sending' | 'sent' | 'error'
|
// and update it here based on the error. e.g. status = 'sending' | 'sent' | 'error'
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -392,7 +388,7 @@ const sendWebSocketEvent = async (
|
|||||||
const lastMessage: WebSocketMessageEvent = {
|
const lastMessage: WebSocketMessageEvent = {
|
||||||
_id: uuidV4(),
|
_id: uuidV4(),
|
||||||
requestId: options.requestId,
|
requestId: options.requestId,
|
||||||
data: options.message,
|
data: options.payload,
|
||||||
direction: 'OUTGOING',
|
direction: 'OUTGOING',
|
||||||
type: 'message',
|
type: 'message',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@ -406,6 +402,19 @@ const sendWebSocketEvent = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendWebSocketEvent = async (
|
||||||
|
options: { payload: string; requestId: string }
|
||||||
|
): Promise<void> => {
|
||||||
|
const ws = WebSocketConnections.get(options.requestId);
|
||||||
|
|
||||||
|
if (!ws) {
|
||||||
|
console.warn('No websocket found for requestId: ' + options.requestId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPayload(ws, options);
|
||||||
|
};
|
||||||
|
|
||||||
const closeWebSocketConnection = async (
|
const closeWebSocketConnection = async (
|
||||||
options: { requestId: string }
|
options: { requestId: string }
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@ -436,14 +445,7 @@ const findMany = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface WebSocketBridgeAPI {
|
export interface WebSocketBridgeAPI {
|
||||||
create: (options: {
|
open: (options: OpenWebSocketRequestOptions) => void;
|
||||||
requestId: string;
|
|
||||||
workspaceId: string;
|
|
||||||
url: string;
|
|
||||||
headers: RequestHeader[];
|
|
||||||
authentication: RequestAuthentication;
|
|
||||||
cookieJar: CookieJar;
|
|
||||||
}) => void;
|
|
||||||
close: typeof closeWebSocketConnection;
|
close: typeof closeWebSocketConnection;
|
||||||
closeAll: typeof closeAllWebSocketConnections;
|
closeAll: typeof closeAllWebSocketConnections;
|
||||||
readyState: {
|
readyState: {
|
||||||
@ -451,11 +453,11 @@ export interface WebSocketBridgeAPI {
|
|||||||
};
|
};
|
||||||
event: {
|
event: {
|
||||||
findMany: typeof findMany;
|
findMany: typeof findMany;
|
||||||
send: (options: { requestId: string; message: string }) => void;
|
send: typeof sendWebSocketEvent;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export const registerWebSocketHandlers = () => {
|
export const registerWebSocketHandlers = () => {
|
||||||
ipcMain.handle('webSocket.create', createWebSocketConnection);
|
ipcMain.handle('webSocket.open', openWebSocketConnection);
|
||||||
ipcMain.handle('webSocket.event.send', (_, options: Parameters<typeof sendWebSocketEvent>[0]) => sendWebSocketEvent(options));
|
ipcMain.handle('webSocket.event.send', (_, options: Parameters<typeof sendWebSocketEvent>[0]) => sendWebSocketEvent(options));
|
||||||
ipcMain.handle('webSocket.close', (_, options: Parameters<typeof closeWebSocketConnection>[0]) => closeWebSocketConnection(options));
|
ipcMain.handle('webSocket.close', (_, options: Parameters<typeof closeWebSocketConnection>[0]) => closeWebSocketConnection(options));
|
||||||
ipcMain.handle('webSocket.closeAll', closeAllWebSocketConnections);
|
ipcMain.handle('webSocket.closeAll', closeAllWebSocketConnections);
|
||||||
|
@ -3,7 +3,7 @@ import { contextBridge, ipcRenderer } from 'electron';
|
|||||||
import type { WebSocketBridgeAPI } from './main/network/websocket';
|
import type { WebSocketBridgeAPI } from './main/network/websocket';
|
||||||
|
|
||||||
const webSocket: WebSocketBridgeAPI = {
|
const webSocket: WebSocketBridgeAPI = {
|
||||||
create: options => ipcRenderer.invoke('webSocket.create', options),
|
open: options => ipcRenderer.invoke('webSocket.open', options),
|
||||||
close: options => ipcRenderer.invoke('webSocket.close', options),
|
close: options => ipcRenderer.invoke('webSocket.close', options),
|
||||||
closeAll: () => ipcRenderer.invoke('webSocket.closeAll'),
|
closeAll: () => ipcRenderer.invoke('webSocket.closeAll'),
|
||||||
readyState: {
|
readyState: {
|
||||||
|
@ -5,7 +5,7 @@ import styled from 'styled-components';
|
|||||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||||
import { executeHotKey } from '../../../common/hotkeys-listener';
|
import { executeHotKey } from '../../../common/hotkeys-listener';
|
||||||
import { getRenderContext, render, RENDER_PURPOSE_SEND } from '../../../common/render';
|
import { getRenderContext, render, RENDER_PURPOSE_SEND } from '../../../common/render';
|
||||||
import { cookieJar } from '../../../models';
|
import * as models from '../../../models';
|
||||||
import { WebSocketRequest } from '../../../models/websocket-request';
|
import { WebSocketRequest } from '../../../models/websocket-request';
|
||||||
import { ReadyState } from '../../context/websocket-client/use-ws-ready-state';
|
import { ReadyState } from '../../context/websocket-client/use-ws-ready-state';
|
||||||
import { OneLineEditor } from '../codemirror/one-line-editor';
|
import { OneLineEditor } from '../codemirror/one-line-editor';
|
||||||
@ -81,22 +81,19 @@ export const WebSocketActionBar: FC<ActionBarProps> = ({ request, workspaceId, e
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
|
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
|
||||||
const { url: rawUrl, headers, authentication, parameters } = request;
|
|
||||||
// Render any nunjucks tags in the url/headers/authentication settings/cookies
|
// Render any nunjucks tags in the url/headers/authentication settings/cookies
|
||||||
const workspaceCookieJar = await cookieJar.getOrCreateForParentId(workspaceId);
|
const workspaceCookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId);
|
||||||
const rendered = await render({
|
const rendered = await render({
|
||||||
url: rawUrl,
|
url: request.url,
|
||||||
headers,
|
headers: request.headers,
|
||||||
authentication,
|
authentication: request.authentication,
|
||||||
parameters,
|
parameters: request.parameters,
|
||||||
workspaceCookieJar,
|
workspaceCookieJar,
|
||||||
}, renderContext);
|
}, renderContext);
|
||||||
const queryString = buildQueryStringFromParams(rendered.parameters);
|
window.main.webSocket.open({
|
||||||
const url = joinUrlAndQueryString(rendered.url, queryString);
|
|
||||||
window.main.webSocket.create({
|
|
||||||
requestId: request._id,
|
requestId: request._id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
url,
|
url: joinUrlAndQueryString(rendered.url, buildQueryStringFromParams(rendered.parameters)),
|
||||||
headers: rendered.headers,
|
headers: rendered.headers,
|
||||||
authentication: rendered.authentication,
|
authentication: rendered.authentication,
|
||||||
cookieJar: rendered.workspaceCookieJar,
|
cookieJar: rendered.workspaceCookieJar,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { FC, FormEvent, useCallback, useEffect, useRef, useState } from 'react';
|
import { buildQueryStringFromParams, joinUrlAndQueryString } from 'insomnia-url';
|
||||||
|
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@ -10,7 +11,7 @@ import { Environment } from '../../../models/environment';
|
|||||||
import { WebSocketRequest } from '../../../models/websocket-request';
|
import { WebSocketRequest } from '../../../models/websocket-request';
|
||||||
import { ReadyState, useWSReadyState } from '../../context/websocket-client/use-ws-ready-state';
|
import { ReadyState, useWSReadyState } from '../../context/websocket-client/use-ws-ready-state';
|
||||||
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
||||||
import { selectActiveRequestMeta, selectSettings } from '../../redux/selectors';
|
import { selectActiveRequestMeta, selectSettings } from '../../redux/selectors';
|
||||||
import { CodeEditor, UnconnectedCodeEditor } from '../codemirror/code-editor';
|
import { CodeEditor, UnconnectedCodeEditor } from '../codemirror/code-editor';
|
||||||
import { AuthDropdown } from '../dropdowns/auth-dropdown';
|
import { AuthDropdown } from '../dropdowns/auth-dropdown';
|
||||||
import { WebSocketPreviewModeDropdown } from '../dropdowns/websocket-preview-mode';
|
import { WebSocketPreviewModeDropdown } from '../dropdowns/websocket-preview-mode';
|
||||||
@ -34,20 +35,20 @@ const SendMessageForm = styled.form({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
});
|
});
|
||||||
const SendButton = styled.button({
|
const SendButton = styled.button<{ isConnected: boolean }>(({ isConnected }) =>
|
||||||
padding: '0 var(--padding-md)',
|
({
|
||||||
marginLeft: 'var(--padding-xs)',
|
padding: '0 var(--padding-md)',
|
||||||
height: '100%',
|
marginLeft: 'var(--padding-xs)',
|
||||||
border: '1px solid var(--hl-lg)',
|
height: '100%',
|
||||||
borderRadius: 'var(--radius-md)',
|
border: '1px solid var(--hl-lg)',
|
||||||
':hover': {
|
borderRadius: 'var(--radius-md)',
|
||||||
filter: 'brightness(0.8)',
|
background: isConnected ? 'var(--color-surprise)' : 'inherit',
|
||||||
},
|
color: isConnected ? 'var(--color-font-surprise)' : 'inherit',
|
||||||
':enabled': {
|
':hover': {
|
||||||
background: 'var(--color-surprise)',
|
filter: 'brightness(0.8)',
|
||||||
color: 'var(--color-font-surprise)',
|
},
|
||||||
},
|
}));
|
||||||
});
|
|
||||||
const PaneSendButton = styled.div({
|
const PaneSendButton = styled.div({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -65,6 +66,7 @@ interface FormProps {
|
|||||||
request: WebSocketRequest;
|
request: WebSocketRequest;
|
||||||
previewMode: string;
|
previewMode: string;
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PayloadTabPanel = styled(TabPanel)({
|
const PayloadTabPanel = styled(TabPanel)({
|
||||||
@ -78,29 +80,46 @@ const WebSocketRequestForm: FC<FormProps> = ({
|
|||||||
request,
|
request,
|
||||||
previewMode,
|
previewMode,
|
||||||
environmentId,
|
environmentId,
|
||||||
|
workspaceId,
|
||||||
}) => {
|
}) => {
|
||||||
const editorRef = useRef<UnconnectedCodeEditor>(null);
|
const editorRef = useRef<UnconnectedCodeEditor>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initMessageText(): Promise<void> {
|
const init = async () => {
|
||||||
const payload = await models.webSocketPayload.getByParentId(request._id);
|
const payload = await models.webSocketPayload.getByParentId(request._id);
|
||||||
const msg = payload?.value || '';
|
const msg = payload?.value || '';
|
||||||
editorRef.current?.codeMirror?.setValue(msg);
|
editorRef.current?.codeMirror?.setValue(msg);
|
||||||
}
|
};
|
||||||
|
|
||||||
initMessageText();
|
init();
|
||||||
}, [request._id]);
|
}, [request._id]);
|
||||||
|
// NOTE: Nunjucks interpolation can throw errors
|
||||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
const interpolateOpenAndSend = async (payload: string) => {
|
||||||
event.preventDefault();
|
|
||||||
const message = editorRef.current?.getValue() || '';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Render any nunjucks tag in the message
|
|
||||||
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
|
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
|
||||||
const renderedMessage = await render(message, renderContext);
|
const renderedMessage = await render(payload, renderContext);
|
||||||
|
const readyState = await window.main.webSocket.readyState.getCurrent({ requestId: request._id });
|
||||||
window.main.webSocket.event.send({ requestId: request._id, message: renderedMessage });
|
if (readyState !== ReadyState.OPEN) {
|
||||||
|
const workspaceCookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId);
|
||||||
|
const rendered = await render({
|
||||||
|
url: request.url,
|
||||||
|
headers: request.headers,
|
||||||
|
authentication: request.authentication,
|
||||||
|
parameters: request.parameters,
|
||||||
|
workspaceCookieJar,
|
||||||
|
}, renderContext);
|
||||||
|
window.main.webSocket.open({
|
||||||
|
requestId: request._id,
|
||||||
|
workspaceId,
|
||||||
|
url: joinUrlAndQueryString(rendered.url, buildQueryStringFromParams(rendered.parameters)),
|
||||||
|
headers: rendered.headers,
|
||||||
|
authentication: rendered.authentication,
|
||||||
|
cookieJar: rendered.workspaceCookieJar,
|
||||||
|
initialPayload: renderedMessage,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.main.webSocket.event.send({ requestId: request._id, payload: renderedMessage });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.type === 'render') {
|
if (err.type === 'render') {
|
||||||
showModal(RequestRenderErrorModal, {
|
showModal(RequestRenderErrorModal, {
|
||||||
@ -140,7 +159,13 @@ const WebSocketRequestForm: FC<FormProps> = ({
|
|||||||
// To allow for disabling rendering of messages based on a per-request setting.
|
// To allow for disabling rendering of messages based on a per-request setting.
|
||||||
// Same as with regular requests
|
// Same as with regular requests
|
||||||
return (
|
return (
|
||||||
<SendMessageForm id="websocketMessageForm" onSubmit={handleSubmit}>
|
<SendMessageForm
|
||||||
|
id="websocketMessageForm"
|
||||||
|
onSubmit={event => {
|
||||||
|
event.preventDefault();
|
||||||
|
interpolateOpenAndSend(editorRef.current?.getValue() || '');
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
manualPrettify
|
manualPrettify
|
||||||
uniquenessKey={request._id}
|
uniquenessKey={request._id}
|
||||||
@ -266,7 +291,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
|
|||||||
<SendButton
|
<SendButton
|
||||||
type="submit"
|
type="submit"
|
||||||
form="websocketMessageForm"
|
form="websocketMessageForm"
|
||||||
disabled={readyState !== ReadyState.OPEN}
|
isConnected={readyState === ReadyState.OPEN}
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
</SendButton>
|
</SendButton>
|
||||||
@ -276,6 +301,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
|
|||||||
request={request}
|
request={request}
|
||||||
previewMode={previewMode}
|
previewMode={previewMode}
|
||||||
environmentId={environment?._id || ''}
|
environmentId={environment?._id || ''}
|
||||||
|
workspaceId={workspaceId}
|
||||||
/>
|
/>
|
||||||
</PayloadTabPanel>
|
</PayloadTabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel">
|
<TabPanel className="react-tabs__tab-panel">
|
||||||
|
Loading…
Reference in New Issue
Block a user