squishy squash connect and send (#5204)

Co-authored-by: jackkav <jackkav@gmail.com>
This commit is contained in:
Mark Kim 2022-09-23 07:47:20 -04:00 committed by GitHub
parent 883753a7df
commit 07833abcb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 74 deletions

View File

@ -91,17 +91,18 @@ const parseResponseAndBuildTimeline = (url: string, incomingMessage: IncomingMes
];
return { timeline, responseHeaders, statusCode, statusMessage, httpVersion };
};
const createWebSocketConnection = async (
interface OpenWebSocketRequestOptions {
requestId: string;
workspaceId: string;
url: string;
headers: RequestHeader[];
authentication: RequestAuthentication;
cookieJar: CookieJar;
initialPayload?: string;
}
const openWebSocketConnection = async (
event: Electron.IpcMainInvokeEvent,
options: {
requestId: string;
workspaceId: string;
url: string;
headers: RequestHeader[];
authentication: RequestAuthentication;
cookieJar: CookieJar;
}
options: OpenWebSocketRequestOptions
): Promise<void> => {
const existingConnection = WebSocketConnections.get(options.requestId);
@ -282,6 +283,10 @@ const createWebSocketConnection = async (
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');
event.sender.send(readyStateChannel, ws.readyState);
if (options.initialPayload) {
sendPayload(ws, { requestId: options.requestId, payload: options.initialPayload });
}
});
ws.addEventListener('message', ({ data }: MessageEvent) => {
@ -369,17 +374,8 @@ const getWebSocketReadyState = async (
return WebSocketConnections.get(options.requestId)?.readyState ?? 0;
};
const sendWebSocketEvent = async (
options: { message: string; requestId: string }
): 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 => {
const sendPayload = async (ws: WebSocket, options: { payload: string; requestId: string }): Promise<void> => {
ws.send(options.payload, error => {
// @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'
if (error) {
@ -392,7 +388,7 @@ const sendWebSocketEvent = async (
const lastMessage: WebSocketMessageEvent = {
_id: uuidV4(),
requestId: options.requestId,
data: options.message,
data: options.payload,
direction: 'OUTGOING',
type: 'message',
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 (
options: { requestId: string }
): Promise<void> => {
@ -436,14 +445,7 @@ const findMany = async (
};
export interface WebSocketBridgeAPI {
create: (options: {
requestId: string;
workspaceId: string;
url: string;
headers: RequestHeader[];
authentication: RequestAuthentication;
cookieJar: CookieJar;
}) => void;
open: (options: OpenWebSocketRequestOptions) => void;
close: typeof closeWebSocketConnection;
closeAll: typeof closeAllWebSocketConnections;
readyState: {
@ -451,11 +453,11 @@ export interface WebSocketBridgeAPI {
};
event: {
findMany: typeof findMany;
send: (options: { requestId: string; message: string }) => void;
send: typeof sendWebSocketEvent;
};
}
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.close', (_, options: Parameters<typeof closeWebSocketConnection>[0]) => closeWebSocketConnection(options));
ipcMain.handle('webSocket.closeAll', closeAllWebSocketConnections);

View File

@ -3,7 +3,7 @@ import { contextBridge, ipcRenderer } from 'electron';
import type { WebSocketBridgeAPI } from './main/network/websocket';
const webSocket: WebSocketBridgeAPI = {
create: options => ipcRenderer.invoke('webSocket.create', options),
open: options => ipcRenderer.invoke('webSocket.open', options),
close: options => ipcRenderer.invoke('webSocket.close', options),
closeAll: () => ipcRenderer.invoke('webSocket.closeAll'),
readyState: {

View File

@ -5,7 +5,7 @@ import styled from 'styled-components';
import { hotKeyRefs } from '../../../common/hotkeys';
import { executeHotKey } from '../../../common/hotkeys-listener';
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 { ReadyState } from '../../context/websocket-client/use-ws-ready-state';
import { OneLineEditor } from '../codemirror/one-line-editor';
@ -81,22 +81,19 @@ export const WebSocketActionBar: FC<ActionBarProps> = ({ request, workspaceId, e
}
try {
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
const workspaceCookieJar = await cookieJar.getOrCreateForParentId(workspaceId);
const workspaceCookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId);
const rendered = await render({
url: rawUrl,
headers,
authentication,
parameters,
url: request.url,
headers: request.headers,
authentication: request.authentication,
parameters: request.parameters,
workspaceCookieJar,
}, renderContext);
const queryString = buildQueryStringFromParams(rendered.parameters);
const url = joinUrlAndQueryString(rendered.url, queryString);
window.main.webSocket.create({
window.main.webSocket.open({
requestId: request._id,
workspaceId,
url,
url: joinUrlAndQueryString(rendered.url, buildQueryStringFromParams(rendered.parameters)),
headers: rendered.headers,
authentication: rendered.authentication,
cookieJar: rendered.workspaceCookieJar,

View File

@ -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 { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import styled from 'styled-components';
@ -10,7 +11,7 @@ import { Environment } from '../../../models/environment';
import { WebSocketRequest } from '../../../models/websocket-request';
import { ReadyState, useWSReadyState } from '../../context/websocket-client/use-ws-ready-state';
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 { AuthDropdown } from '../dropdowns/auth-dropdown';
import { WebSocketPreviewModeDropdown } from '../dropdowns/websocket-preview-mode';
@ -34,20 +35,20 @@ const SendMessageForm = styled.form({
position: 'relative',
boxSizing: 'border-box',
});
const SendButton = styled.button({
padding: '0 var(--padding-md)',
marginLeft: 'var(--padding-xs)',
height: '100%',
border: '1px solid var(--hl-lg)',
borderRadius: 'var(--radius-md)',
':hover': {
filter: 'brightness(0.8)',
},
':enabled': {
background: 'var(--color-surprise)',
color: 'var(--color-font-surprise)',
},
});
const SendButton = styled.button<{ isConnected: boolean }>(({ isConnected }) =>
({
padding: '0 var(--padding-md)',
marginLeft: 'var(--padding-xs)',
height: '100%',
border: '1px solid var(--hl-lg)',
borderRadius: 'var(--radius-md)',
background: isConnected ? 'var(--color-surprise)' : 'inherit',
color: isConnected ? 'var(--color-font-surprise)' : 'inherit',
':hover': {
filter: 'brightness(0.8)',
},
}));
const PaneSendButton = styled.div({
display: 'flex',
flexDirection: 'row',
@ -65,6 +66,7 @@ interface FormProps {
request: WebSocketRequest;
previewMode: string;
environmentId: string;
workspaceId: string;
}
const PayloadTabPanel = styled(TabPanel)({
@ -78,29 +80,46 @@ const WebSocketRequestForm: FC<FormProps> = ({
request,
previewMode,
environmentId,
workspaceId,
}) => {
const editorRef = useRef<UnconnectedCodeEditor>(null);
useEffect(() => {
async function initMessageText(): Promise<void> {
const init = async () => {
const payload = await models.webSocketPayload.getByParentId(request._id);
const msg = payload?.value || '';
editorRef.current?.codeMirror?.setValue(msg);
}
};
initMessageText();
init();
}, [request._id]);
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const message = editorRef.current?.getValue() || '';
// NOTE: Nunjucks interpolation can throw errors
const interpolateOpenAndSend = async (payload: string) => {
try {
// Render any nunjucks tag in the message
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
const renderedMessage = await render(message, renderContext);
window.main.webSocket.event.send({ requestId: request._id, message: renderedMessage });
const renderedMessage = await render(payload, renderContext);
const readyState = await window.main.webSocket.readyState.getCurrent({ requestId: request._id });
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) {
if (err.type === 'render') {
showModal(RequestRenderErrorModal, {
@ -140,7 +159,13 @@ const WebSocketRequestForm: FC<FormProps> = ({
// To allow for disabling rendering of messages based on a per-request setting.
// Same as with regular requests
return (
<SendMessageForm id="websocketMessageForm" onSubmit={handleSubmit}>
<SendMessageForm
id="websocketMessageForm"
onSubmit={event => {
event.preventDefault();
interpolateOpenAndSend(editorRef.current?.getValue() || '');
}}
>
<CodeEditor
manualPrettify
uniquenessKey={request._id}
@ -266,7 +291,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
<SendButton
type="submit"
form="websocketMessageForm"
disabled={readyState !== ReadyState.OPEN}
isConnected={readyState === ReadyState.OPEN}
>
Send
</SendButton>
@ -276,6 +301,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
request={request}
previewMode={previewMode}
environmentId={environment?._id || ''}
workspaceId={workspaceId}
/>
</PayloadTabPanel>
<TabPanel className="react-tabs__tab-panel">