diff --git a/package.json b/package.json index 13eae67..092c739 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@types/node": "^12.11.7", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.4", + "@types/react-redux": "^7.1.16", "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^4.14.1", "@typescript-eslint/parser": "^4.14.1", @@ -99,10 +100,16 @@ "webpack-cli": "^4.4.0" }, "dependencies": { + "@reduxjs/toolkit": "^1.5.1", "axios": "^0.21.1", + "buffer": "^6.0.3", "monaco-editor": "^0.24.0", + "path-browserify": "^1.0.1", + "postman-collection": "^3.6.11", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-icons": "^4.2.0" + "react-icons": "^4.2.0", + "react-redux": "^7.2.4", + "url": "^0.11.0" } } diff --git a/src/extension.ts b/src/extension.ts index f70e7ff..f16775f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,77 +53,88 @@ export function activate(context: vscode.ExtensionContext) { `; panel.webview.onDidReceiveMessage( - ({ - reqType, - requestUrl, - headers, - body, - auth, - selectedBodyType, - rawLanguage, - }) => { - if (requestUrl) { - const headersObj = {}; + ({ method, url, headers, body, auth }) => { + if (!url) { + panel.webview.postMessage({ + type: "response", + error: { message: "Request URL is empty" }, + }); + vscode.window.showInformationMessage("Request URL is empty"); + return; + } + + const headersObj = {}; - if (selectedBodyType === "form-data") { - headersObj["Content-Type"] = "multipart/form-data"; - } else if (selectedBodyType === "urlcoded") { - headersObj["Content-Type"] = "application/x-www-form-urlencoded"; - } else if (selectedBodyType === "raw" && rawLanguage === "json") { - headersObj["Content-Type"] = "application/json"; - } else if (selectedBodyType === "raw" && rawLanguage === "html") { - headersObj["Content-Type"] = "text/html"; - } else if (selectedBodyType === "raw" && rawLanguage === "xml") { - headersObj["Content-Type"] = "text/xml"; - } else if (selectedBodyType === "raw" && rawLanguage === "text") { - headersObj["Content-Type"] = "text/plain"; - } else if (selectedBodyType === "binary") { - headersObj["Content-Type"] = "application/octet-stream"; + if (auth.type === "bearer") { + headersObj["Authorization"] = `Bearer ${auth.bearer.token}`; + } + + headers.forEach(({ key, value, disabled }) => { + if (!disabled) { + headersObj[key] = value; } + }); - headers.forEach(({ key, value, checked }) => { - if (checked) { - headersObj[key || ""] = value || ""; + let data = ""; + if (body.mode === "form-data") { + const dataObj = new URLSearchParams(); + body.formdata.forEach(({ key, value, disabled }) => { + if (!disabled) { + dataObj.append(key, value); } }); + data = dataObj.toString(); + headersObj["Content-Type"] = "multipart/form-data"; + } else if (body.mode === "x-www-form-urlencoded") { + const dataObj = new URLSearchParams(); + body.urlencoded.forEach(({ key, value, disabled }) => { + if (!disabled) { + dataObj.append(key, value); + } + }); + data = dataObj.toString(); + headersObj["Content-Type"] = "application/x-www-form-urlencoded"; + } else if (body.mode === "raw") { + data = body.raw; + headersObj["Content-Type"] = { + json: "application/json", + html: "text/html", + xml: "text/xml", + text: "text/plain", + }[body.options.raw.language]; + } else if (body.mode === "binary") { + data = body.fileData; + headersObj["Content-Type"] = "application/octet-stream"; + } - if (auth.selected === "bearer-token") { - headersObj["Authorization"] = `Bearer ${auth.token}`; - } - - axios({ - method: reqType, - url: requestUrl, - data: body, - headers: headersObj, - auth: - auth.selected === "basic-auth" - ? { username: auth.username, password: auth.password } - : undefined, - transformResponse: [(data) => data], - responseType: "text", - validateStatus: () => true, - }) - .then((resp) => - panel.webview.postMessage({ - type: "response", - data: resp.data, - status: resp.status, - statusText: resp.statusText, - }) - ) - .catch((err) => { - panel.webview.postMessage({ - type: "response", - error: err, - }); - vscode.window.showInformationMessage( - "Error: Could not send request" - ); + axios({ + method, + url, + baseURL: "", + data: data, + headers: headersObj, + auth: auth.type === "basic-auth" ? auth.basic : undefined, + transformResponse: [(data) => data], + responseType: "text", + validateStatus: () => true, + }) + .then((resp) => + panel.webview.postMessage({ + type: "response", + data: resp.data, + status: resp.status, + statusText: resp.statusText, + }) + ) + .catch((err) => { + panel.webview.postMessage({ + type: "response", + error: err, }); - } else { - vscode.window.showInformationMessage("Request URL is empty"); - } + vscode.window.showInformationMessage( + "Error: Could not send request" + ); + }); } ); } diff --git a/webpack.config.js b/webpack.config.js index e47f247..1e64862 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ "use strict"; const path = require("path"); +const webpack = require("webpack"); const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); const imageInlineSizeLimit = parseInt( @@ -19,7 +20,14 @@ const baseConfig = (webpackEnv) => { devtool: isEnvProduction ? "source-map" : isEnvDevelopment && "eval-cheap-module-source-map", - resolve: { extensions: [".ts", ".tsx", ".js"] }, + resolve: { + fallback: { + buffer: require.resolve("buffer"), + path: require.resolve("path-browserify"), + url: require.resolve("url"), + }, + extensions: [".ts", ".tsx", ".js"], + }, module: { rules: [ { @@ -98,6 +106,9 @@ const webviewConfig = (webpackEnv) => { new MonacoWebpackPlugin({ languages: ["html", "xml", "json"], }), + new webpack.ProvidePlugin({ + Buffer: ["buffer", "Buffer"], + }), ], }; }; diff --git a/webview/App.tsx b/webview/App.tsx index 4e1dd01..e4af26b 100644 --- a/webview/App.tsx +++ b/webview/App.tsx @@ -1,28 +1,23 @@ import * as React from "react"; import "./App.css"; +import { responseUpdated } from "./features/response/responseSlice"; import { Postcode } from "./pages/Postcode"; +import { useAppDispatch } from "./redux/hooks"; const App = () => { - const [response, setResponse] = React.useState({ initial: "true" }); - const [loadingResponse, setLoadingResponse] = React.useState(false); + const dispatch = useAppDispatch(); React.useEffect(() => { window.addEventListener("message", (event) => { if (event.data.type === "response") { - setLoadingResponse(false); - setResponse(event.data); + dispatch(responseUpdated(event.data)); } }); }, []); return (
- +
); }; diff --git a/webview/components/AuthorizationWindow/index.tsx b/webview/components/AuthorizationWindow/index.tsx deleted file mode 100644 index 3042125..0000000 --- a/webview/components/AuthorizationWindow/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { NoAuth } from "../NoAuthTab"; -import { BearerToken } from "../BearerTokenTab"; -import { BasicAuth } from "../BasicAuthTab"; -import { authTypes } from "../../constants/auth-types"; -import * as propTypes from "prop-types"; - -export const Authorization = (props) => { - const { auth, setAuth } = props; - // move to parent to preserve state - return ( -
-
-
Authorization Type:
- -
-
- {auth.selected === "no-auth" ? ( - - ) : auth.selected === "bearer-token" ? ( - - ) : auth.selected === "basic-auth" ? ( - - ) : null} -
-
- ); -}; - -Authorization.propTypes = { - auth: propTypes.object.isRequired, - setAuth: propTypes.func.isRequired, -}; diff --git a/webview/components/BasicAuthTab/index.tsx b/webview/components/BasicAuthTab/index.tsx deleted file mode 100644 index 62c7f06..0000000 --- a/webview/components/BasicAuthTab/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import * as propTypes from "prop-types"; - -export const BasicAuth = (props) => { - const { auth, setAuth } = props; - return ( -
-
-
Username
- { - setAuth({ ...auth, username: e.target.value }); - }} - /> -
-
-
Password
- { - setAuth({ ...auth, password: e.target.value }); - }} - /> -
-
- ); -}; - -BasicAuth.propTypes = { - auth: propTypes.object.isRequired, - setAuth: propTypes.func.isRequired, -}; diff --git a/webview/components/BearerTokenTab/index.tsx b/webview/components/BearerTokenTab/index.tsx deleted file mode 100644 index 0b06cf9..0000000 --- a/webview/components/BearerTokenTab/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import * as propTypes from "prop-types"; - -export const BearerToken = (props) => { - const { auth, setAuth } = props; - return ( -
-
Token
- setAuth({ ...auth, token: e.target.value })} - /> -
- ); -}; - -BearerToken.propTypes = { - auth: propTypes.object.isRequired, - setAuth: propTypes.func.isRequired, -}; diff --git a/webview/components/Binary/index.tsx b/webview/components/Binary/index.tsx deleted file mode 100644 index 6deb408..0000000 --- a/webview/components/Binary/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import * as propTypes from "prop-types"; -import "./styles.css"; - -export const Binary = (props) => { - const { binary, setBinary } = props; - - return ( -
-
- { - if (e.target.files.length) { - setBinary(e.target.files[0]); - } - }} - > - -
{binary.name}
-
-
- ); -}; - -Binary.propTypes = { - binary: propTypes.any.isRequired, - setBinary: propTypes.any.isRequired, -}; diff --git a/webview/components/BodyWindow/index.tsx b/webview/components/BodyWindow/index.tsx deleted file mode 100644 index 83f120f..0000000 --- a/webview/components/BodyWindow/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; -import { ReqBodyTab } from "../BodyWindowBar"; -import { ReqBodyWindow } from "../BodyWindowContent"; -import * as propTypes from "prop-types"; -import "./styles.css"; - -export const Body = (props) => { - const { - setBody, - formData, - setFormData, - urlCoded, - setUrlCoded, - binary, - setBinary, - raw, - setRaw, - selectedBodyType, - setSelectedBodyType, - rawLanguage, - setRawLanguage, - } = props; - - return ( -
- - -
- ); -}; - -Body.propTypes = { - setBody: propTypes.func.isRequired, - formData: propTypes.any.isRequired, - setFormData: propTypes.any.isRequired, - urlCoded: propTypes.any.isRequired, - setUrlCoded: propTypes.any.isRequired, - binary: propTypes.any.isRequired, - setBinary: propTypes.any.isRequired, - raw: propTypes.any.isRequired, - setRaw: propTypes.any.isRequired, - selectedBodyType: propTypes.any.isRequired, - setSelectedBodyType: propTypes.any.isRequired, - rawLanguage: propTypes.any.isRequired, - setRawLanguage: propTypes.any.isRequired, -}; diff --git a/webview/components/BodyWindow/styles.css b/webview/components/BodyWindow/styles.css deleted file mode 100644 index 9a10da2..0000000 --- a/webview/components/BodyWindow/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -.request-body-wrapper { - display: flex; - flex-direction: column; - height: 100%; -} diff --git a/webview/components/BodyWindowBar/index.tsx b/webview/components/BodyWindowBar/index.tsx deleted file mode 100644 index c347dc2..0000000 --- a/webview/components/BodyWindowBar/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import * as propTypes from "prop-types"; -import { bodyTypes } from "../../constants/body-types"; -import { supportedLangs } from "../../constants/supported-langs"; - -export const ReqBodyTab = (props) => { - const { selected, setSelected, language, setLanguage } = props; - return ( -
- {bodyTypes.map((option) => ( -
- setSelected(option.value)} - className="radio-body-option" - defaultChecked={selected === option.value ? true : false} - /> - -
- ))} - {selected === "raw" ? ( - - ) : null} -
- ); -}; - -ReqBodyTab.propTypes = { - selected: propTypes.string.isRequired, - setSelected: propTypes.func.isRequired, - language: propTypes.string.isRequired, - setLanguage: propTypes.func.isRequired, -}; diff --git a/webview/components/BodyWindowContent/index.tsx b/webview/components/BodyWindowContent/index.tsx deleted file mode 100644 index 6b4218c..0000000 --- a/webview/components/BodyWindowContent/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from "react"; -import { None } from "../NoBodyTab"; -import { FormData } from "../FormDataTab"; -import { UrlCoded } from "../UrlCodedTab"; -import { Raw } from "../RawTab"; -import { Binary } from "../Binary"; -import * as propTypes from "prop-types"; -import "./styles.css"; - -export const ReqBodyWindow = (props) => { - const { - selected, - setBody, - language, - formData, - setFormData, - urlCoded, - setUrlCoded, - binary, - setBinary, - raw, - setRaw, - } = props; - - React.useEffect(() => { - if (selected === "none") { - setBody(""); - } else if (selected === "form-data") { - const data = new URLSearchParams(); - formData.forEach((item: { key?: string; value?: string }) => { - data.append(item.key || "", item.value || ""); - }); - setBody(data.toString()); - } else if (selected === "urlcoded") { - const data = new URLSearchParams(); - urlCoded.forEach((item: { key?: string; value?: string }) => { - data.append(item.key || "", item.value || ""); - }); - setBody(data.toString()); - } else if (selected === "raw") { - setBody(raw); - } else if (selected === "binary") { - const setBinaryBody = async () => { - setBody(await binary.text()); - }; - setBinaryBody(); - } - }, [formData, urlCoded, raw, binary, selected]); - - return ( -
- {selected === "none" ? ( - - ) : selected === "form-data" ? ( - - ) : selected === "urlcoded" ? ( - - ) : selected === "raw" ? ( - - ) : selected === "binary" ? ( - - ) : null} -
- ); -}; - -ReqBodyWindow.propTypes = { - selected: propTypes.string.isRequired, - setBody: propTypes.func.isRequired, - language: propTypes.string.isRequired, - formData: propTypes.any.isRequired, - setFormData: propTypes.any.isRequired, - urlCoded: propTypes.any.isRequired, - setUrlCoded: propTypes.any.isRequired, - binary: propTypes.any.isRequired, - setBinary: propTypes.any.isRequired, - raw: propTypes.any.isRequired, - setRaw: propTypes.any.isRequired, -}; diff --git a/webview/components/BodyWindowContent/styles.css b/webview/components/BodyWindowContent/styles.css deleted file mode 100644 index 2923fb0..0000000 --- a/webview/components/BodyWindowContent/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.request-body-window-wrapper { - flex: 1; -} diff --git a/webview/components/FormDataTab/index.tsx b/webview/components/FormDataTab/index.tsx deleted file mode 100644 index 5e01988..0000000 --- a/webview/components/FormDataTab/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { KeyValueTable } from "../../shared/KeyValueTable"; -import * as propTypes from "prop-types"; - -export const FormData = (props) => { - const { formData, setFormData } = props; - return ( -
- -
- ); -}; - -FormData.propTypes = { - formData: propTypes.array.isRequired, - setFormData: propTypes.func.isRequired, -}; diff --git a/webview/components/HeadersWindow/index.tsx b/webview/components/HeadersWindow/index.tsx deleted file mode 100644 index e3ca3e4..0000000 --- a/webview/components/HeadersWindow/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { KeyValueTable } from "../../shared/KeyValueTable"; -import * as propTypes from "prop-types"; - -export const Headers = (props) => { - const { headers, setHeaders } = props; - return ( -
- -
- ); -}; - -Headers.propTypes = { - headers: propTypes.array.isRequired, - setHeaders: propTypes.func.isRequired, -}; diff --git a/webview/components/ParamsWindow/index.tsx b/webview/components/ParamsWindow/index.tsx deleted file mode 100644 index ea07dc1..0000000 --- a/webview/components/ParamsWindow/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { KeyValueTable } from "../../shared/KeyValueTable"; -import * as propTypes from "prop-types"; - -export const Params = (props) => { - const { params, setParams } = props; - return ( -
- -
- ); -}; - -Params.propTypes = { - params: propTypes.array.isRequired, - setParams: propTypes.func.isRequired, -}; diff --git a/webview/components/RawTab/index.tsx b/webview/components/RawTab/index.tsx deleted file mode 100644 index 2673a2a..0000000 --- a/webview/components/RawTab/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; -import * as propTypes from "prop-types"; -import { Editor } from "../../shared/Editor"; -import "./styles.css"; - -export const Raw = (props) => { - const { raw, setRaw, language } = props; - return ( -
- -
- ); -}; - -Raw.propTypes = { - raw: propTypes.string.isRequired, - setRaw: propTypes.func.isRequired, - language: propTypes.string.isRequired, -}; diff --git a/webview/components/RequestBar/index.tsx b/webview/components/RequestBar/index.tsx index 97e8b7b..8319e9d 100644 --- a/webview/components/RequestBar/index.tsx +++ b/webview/components/RequestBar/index.tsx @@ -1,48 +1,42 @@ import * as React from "react"; +import vscode from "../../vscode"; +import { RequestMethodSelector } from "../../features/requestMethod/RequestMethodSelector"; +import { RequestUrl } from "../../features/requestUrl/RequestUrl"; +import { responseLoadingStarted } from "../../features/response/responseSlice"; +import { selectRequestAuth } from "../../features/requestAuth/requestAuthSlice"; +import { selectRequestBody } from "../../features/requestBody/requestBodySlice"; +import { selectRequestHeaders } from "../../features/requestHeader/requestHeaderSlice"; +import { selectRequestUrl } from "../../features/requestUrl/requestUrlSlice"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks"; import "./styles.css"; -import { requestTypes } from "../../constants/request-types"; -import * as propTypes from "prop-types"; +import { selectRequestMethod } from "../../features/requestMethod/requestMethodSlice"; + +export const RequestBar = () => { + const dispatch = useAppDispatch(); + + const requestMethod = useAppSelector(selectRequestMethod); + const requestHeaders = useAppSelector(selectRequestHeaders); + const requestBody = useAppSelector(selectRequestBody); + const requestUrl = useAppSelector(selectRequestUrl); + const requestAuth = useAppSelector(selectRequestAuth); -export const RequestBar = (props) => { - const { - requestUrl, - setRequestUrl, - sendRequest, - setReqType, - setLoadingResponse, - } = props; return (
{ - sendRequest(); - if (requestUrl !== "") { - setLoadingResponse(true); - } + dispatch(responseLoadingStarted()); + vscode.postMessage({ + method: requestMethod, + auth: requestAuth, + body: requestBody, + headers: requestHeaders, + url: requestUrl, + }); e.preventDefault(); }} > - - setRequestUrl(e.target.value)} - /> + +