From 8382e6099859be1a70c2a1576ca531a360cfe080 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Fri, 21 Jul 2023 14:41:29 +0200 Subject: [PATCH] Add very basic Send UI (feature flagged) for initial testing Can send requests, accepts all the core inputs we'll need and persists them, has the top-level UI structure & data model sort-of in place. Does not show responses (they're just logged to the console), does not do lots of very necessary UX details, and doesn't have any kind of UI polish at all. But exciting! --- src/components/app.tsx | 32 ++++- src/components/send/request-pane.tsx | 173 ++++++++++++++++++++++++++ src/components/send/response-pane.tsx | 18 +++ src/components/send/send-page.tsx | 33 +++++ src/icons.ts | 2 + src/index.tsx | 8 +- src/model/rules/rules-store.ts | 11 +- src/model/send/send-data-model.ts | 96 ++++++++++++++ src/model/send/send-store.ts | 27 ++++ src/services/server-api.ts | 17 ++- src/services/server-rest-api.ts | 134 ++++++++++++++++++-- src/services/service-versions.ts | 3 +- src/util/streams.ts | 64 ++++++++++ 13 files changed, 597 insertions(+), 21 deletions(-) create mode 100644 src/components/send/request-pane.tsx create mode 100644 src/components/send/response-pane.tsx create mode 100644 src/components/send/send-page.tsx create mode 100644 src/model/send/send-data-model.ts create mode 100644 src/model/send/send-store.ts create mode 100644 src/util/streams.ts diff --git a/src/components/app.tsx b/src/components/app.tsx index ef264c44..185b0af1 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -16,13 +16,20 @@ import { appHistory } from '../routing'; import { useHotkeys, Ctrl } from '../util/ui'; import { AccountStore } from '../model/account/account-store'; -import { serverVersion, versionSatisfies, MOCK_SERVER_RANGE } from '../services/service-versions'; +import { + serverVersion, + versionSatisfies, + MOCK_SERVER_RANGE, + SERVER_SEND_API_SUPPORTED +} from '../services/service-versions'; import { Sidebar, SidebarItem, SIDEBAR_WIDTH } from './sidebar'; import { InterceptPage } from './intercept/intercept-page'; import { ViewPage } from './view/view-page'; import { MockPage } from './mock/mock-page'; +import { SendPage } from './send/send-page'; import { SettingsPage } from './settings/settings-page'; + import { PlanPicker } from './account/plan-picker'; import { ModalOverlay } from './account/modal-overlay'; import { CheckoutSpinner } from './account/checkout-spinner'; @@ -83,6 +90,16 @@ class App extends React.Component<{ accountStore: AccountStore }> { return this.props.accountStore.isPaidUser || this.props.accountStore.isPastDueUser; } + @computed + get canVisitSend() { + return this.props.accountStore.featureFlags.includes('send') && ( + // Hide Send option if the server is too old for proper support. + // We show by default to avoid flicker in the most common case + serverVersion.state !== 'fulfilled' || + versionSatisfies(serverVersion.value as string, SERVER_SEND_API_SUPPORTED) + ); + } + @computed get menuItems() { return [ @@ -121,6 +138,18 @@ class App extends React.Component<{ accountStore: AccountStore }> { : [] ), + ...(this.canVisitSend + ? [{ + name: 'Send', + title: `Send HTTP requests directly (${Ctrl}+4)`, + icon: ['far', 'paper-plane'], + position: 'top', + type: 'router', + url: '/send' + }] + : [] + ), + (this.canVisitSettings ? { name: 'Settings', @@ -191,6 +220,7 @@ class App extends React.Component<{ accountStore: AccountStore }> { + diff --git a/src/components/send/request-pane.tsx b/src/components/send/request-pane.tsx new file mode 100644 index 00000000..c9bf3622 --- /dev/null +++ b/src/components/send/request-pane.tsx @@ -0,0 +1,173 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { action, computed } from 'mobx'; +import { inject, observer } from 'mobx-react'; +import { + MOCKTTP_PARAM_REF, + ProxySetting, + ProxySettingSource, + RuleParameterReference +} from 'mockttp'; + +import { RawHeaders } from '../../types'; +import { styled } from '../../styles'; +import { bufferToString, isProbablyUtf8, stringToBuffer } from '../../util'; + +import { sendRequest } from '../../services/server-api'; +import { RulesStore } from '../../model/rules/rules-store'; +import { SendStore } from '../../model/send/send-store'; +import { ClientProxyConfig, RULE_PARAM_REF_KEY } from '../../model/send/send-data-model'; + +import { EditableRawHeaders } from '../common/editable-headers'; +import { ThemedSelfSizedEditor } from '../editor/base-editor'; +import { Button, TextInput } from '../common/inputs'; + +const RequestPaneContainer = styled.section` + display: flex; + flex-direction: column; +`; + +const UrlInput = styled(TextInput)` +`; + +export const getEffectivePort = (url: { protocol: string | null, port: string | null }) => { + if (url.port) { + return parseInt(url.port, 10); + } else if (url.protocol === 'https:' || url.protocol === 'wss:') { + return 443; + } else { + return 80; + } +} + +@inject('rulesStore') +@inject('sendStore') +@observer +export class RequestPane extends React.Component<{ + rulesStore?: RulesStore, + sendStore?: SendStore +}> { + + @computed + get method() { + return this.props.sendStore!.requestInput.method; + } + + @computed + get url() { + return this.props.sendStore!.requestInput.url; + } + + + @computed + get headers() { + return this.props.sendStore!.requestInput.headers; + } + + @computed + get body() { + return this.props.sendStore!.requestInput.rawBody; + } + + @computed + private get bodyTextEncoding() { + // If we're handling text data, we want to show & edit it as UTF8. + // If it's binary, that's a lossy operation, so we use binary (latin1) instead. + return isProbablyUtf8(this.body) + ? 'utf8' + : 'binary'; + } + + render() { + const bodyString = bufferToString(this.body, this.bodyTextEncoding); + + return + + + +