├── .eslintrc.json ├── .gitignore ├── .husky └── pre-push ├── .prettierignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── icons └── icon.png ├── package.json ├── src ├── extension.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json ├── webpack.config.js ├── webview ├── App.css ├── App.tsx ├── acquireVsCodeApi.d.ts ├── components │ ├── RequestBar │ │ ├── index.tsx │ │ └── styles.css │ ├── RequestOptionsBar │ │ ├── index.tsx │ │ └── styles.css │ ├── RequestOptionsWindow │ │ ├── index.tsx │ │ └── styles.css │ └── Response │ │ ├── index.tsx │ │ └── styles.css ├── constants │ ├── request-options.ts │ ├── response-options.ts │ ├── response-views.ts │ └── supported-langs.ts ├── features │ ├── codeGen │ │ ├── CodeSnippet │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── codeGenSlice.ts │ ├── requestAuth │ │ ├── BasicAuth │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── BearerToken │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── NoAuth │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── RequestAuth │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestAuthSlice.ts │ ├── requestBody │ │ ├── Binary │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── FormData │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── GraphQL │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── NoBody │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Raw │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── RequestBody │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── UrlEncoded │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestBodySlice.ts │ ├── requestHeader │ │ ├── HeadersWindow │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestHeaderSlice.ts │ ├── requestMethod │ │ ├── RequestMethodSelector │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestMethodSlice.ts │ ├── requestOptions │ │ ├── RequestOptions │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestOptionsSlice.ts │ ├── requestUrl │ │ ├── RequestQueryParams │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── RequestUrl │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ └── requestUrlSlice.ts │ └── response │ │ ├── ResponseBody │ │ ├── index.tsx │ │ └── styles.css │ │ ├── ResponseHeaders │ │ ├── index.tsx │ │ └── styles.css │ │ ├── ResponseTab │ │ ├── index.tsx │ │ └── styles.css │ │ ├── ResponseWindow │ │ ├── index.tsx │ │ └── styles.css │ │ └── responseSlice.ts ├── icons │ └── package.svg ├── index.css ├── index.tsx ├── pages │ └── Postcode │ │ ├── index.tsx │ │ └── styles.css ├── prerender.tsx ├── react-app-env.d.ts ├── redux │ ├── hooks.ts │ └── store.ts ├── shared │ ├── Editor │ │ ├── index.tsx │ │ └── styles.css │ └── KeyValueTable │ │ ├── index.tsx │ │ └── styles.css └── vscode.ts └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:prettier/recommended" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 12, 19 | "sourceType": "module" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detect" 24 | } 25 | }, 26 | "ignorePatterns": ["out", "dist", "**/*.d.ts"], 27 | "plugins": ["react", "@typescript-eslint"], 28 | "rules": { 29 | "@typescript-eslint/explicit-module-boundary-types": "off" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 13 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 14 | "preLaunchTask": "${defaultBuildTask}" 15 | }, 16 | { 17 | "name": "Extension Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "args": [ 21 | "--extensionDevelopmentPath=${workspaceFolder}", 22 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 23 | ], 24 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 25 | "preLaunchTask": "npm: test-watch" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"], 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "type": "npm", 21 | "script": "test-watch", 22 | "problemMatcher": "$tsc-watch", 23 | "isBackground": true, 24 | "presentation": { 25 | "reveal": "never" 26 | }, 27 | "group": "build" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | vsc-extension-quickstart.md 9 | **/tsconfig.json 10 | **/.eslintrc.json 11 | **/*.map 12 | **/*.ts 13 | dist/prerender.js 14 | dist/*.prerender.js 15 | dist/*.prerender.js.* 16 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --ignore-engines true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "postcode" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ### 1.3.9 8 | 9 | - automattically use http protocol if none is specified 10 | - make font size relative to vscode editor font size 11 | 12 | ### 1.3.8 13 | 14 | - display response time in response window 15 | 16 | ### 1.3.7 17 | 18 | - add request options for strict ssl 19 | 20 | ### 1.3.6 21 | 22 | - add beautify button for post body 23 | 24 | ### 1.3.5 25 | 26 | - fix basic auth functionality 27 | 28 | ### 1.3.4 29 | 30 | - Add headers tab for response 31 | 32 | ### 1.3.3 33 | 34 | - Improve load time by pre-rendering 35 | 36 | ### 1.3.2 37 | 38 | - Remove excess scroll 39 | 40 | ### 1.3.1 41 | 42 | - Add copy button to code editor 43 | 44 | ### 1.3.0 45 | 46 | - Support code-snippet generation 47 | 48 | ### 1.2.1 49 | 50 | - Clear error when response is updated 51 | 52 | ### 1.2.0 53 | 54 | - Support GraphQL requests 55 | 56 | ### 1.1.0 57 | 58 | - Support formating of responses based on language 59 | 60 | ### 1.0.0 61 | 62 | - Initial release 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rohini Senthil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Postcode

2 |
3 | API client for VS code 📦 4 |

5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | Postcode is a [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) that can be used to create and test simple and complex HTTP/s requests, as well as view responses. You can find the extension available [here](https://marketplace.visualstudio.com/items?itemName=rohinivsenthil.postcode). 13 | 14 | 15 |
16 | 17 |
18 | Release: 1.3.3 19 |
20 | 21 | ## Highlighted Features 22 | 23 | - **Intuitive UI/UX** similar to Postman fitting seamlessly with any VSCode theme 24 | - Supports **GraphQL** requests 25 | - Supports **code snippet generation** from requests 26 | 27 | ## Quick start 28 | 29 | **Step 1.** Install the Postcode extension for Visual Studio Code 30 | **Step 2.** Click on the Postcode icon in the side panel OR run the following command **Postcode: Create Request** 31 | **Step 3** Create or test your HTTP/s requests and hit Send to see the responses 32 | 33 | ## Commands 34 | 35 | | Command | Description | 36 | | ------------------------ | ---------------------------------------------------- | 37 | | Postcode: Create Request | Opens a new Postcode tab to create and test requests | 38 | 39 | ## Issues, feature requests, and contributions 40 | 41 | ### Issues 42 | 43 | - If you come across a problem with the extension, please file an [issue](https://github.com/rohinivsenthil/postcode/issues/new) 44 | - For list of known issues, please check the [issues tab](https://github.com/rohinivsenthil/postcode/issues/new) 45 | 46 | ### Feature requests 47 | 48 | - Find planned features for future releases marked as [feature](https://github.com/rohinivsenthil/postcode/issues?q=is%3Aissue+is%3Aopen+label%3Afeature) under issues tab. 49 | - For new feature requests, please file an [issue](https://github.com/rohinivsenthil/postcode/issues/new) 50 | 51 | ### Contributions 52 | 53 | Contributions are always welcome! 54 | 55 | #### Running the extension locally for development 56 | 57 | 1. Clone the repository and install dependencies by running `yarn install` 58 | 2. Press `F5` to open a new window with your extension loaded. 59 | 3. Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Postcode: Create Request`. 60 | 61 | #### Folder structure 62 | 63 | - **`package.json`** - this is the manifest file in which you declare your extension and command. The plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. 64 | - **`webview`**: folder where you will find entire React code 65 | - **`src/extension.ts`**: this is the main file where you will provide the implementation of your command. The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 66 | 67 | #### Making changes 68 | 69 | - You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 70 | - You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 71 | 72 | ## Related 73 | 74 | - Read the [launch blog](https://rohinivsenthil.medium.com/postcode-vs-code-extension-alternative-to-postman-384816d4cf07) post on Medium 75 | - Featured #11 Product of the day on [Product Hunt](https://www.producthunt.com/posts/postcode) 76 | - Featured in **Trending this week** on Visual Studio Code Marketplace 77 | -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohinivsenthil/postcode/824ff7d0c2d04f7204ad7461ffd1fb105cdcc14d/icons/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcode", 3 | "publisher": "rohinivsenthil", 4 | "displayName": "Postcode", 5 | "icon": "icons/icon.png", 6 | "description": "An API client to test and create HTTP/s requests", 7 | "version": "1.3.9", 8 | "license": "MIT", 9 | "bugs": { 10 | "url": "https://github.com/rohinivsenthil/postcode/issues" 11 | }, 12 | "author": { 13 | "name": "Rohini Senthil", 14 | "email": "rohinivsenthil@gmail.com" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/rohinivsenthil/postcode" 19 | }, 20 | "engines": { 21 | "vscode": "^1.56.0" 22 | }, 23 | "categories": [ 24 | "Programming Languages" 25 | ], 26 | "keywords": [ 27 | "api-client", 28 | "postman", 29 | "REST", 30 | "graphql", 31 | "http" 32 | ], 33 | "activationEvents": [ 34 | "onCommand:postcode.createRequest" 35 | ], 36 | "main": "./dist/extension.js", 37 | "contributes": { 38 | "commands": [ 39 | { 40 | "command": "postcode.createRequest", 41 | "title": "Postcode: Create Request" 42 | } 43 | ], 44 | "viewsContainers": { 45 | "activitybar": [ 46 | { 47 | "id": "postcode", 48 | "title": "Postcode", 49 | "icon": "webview/icons/package.svg" 50 | } 51 | ] 52 | }, 53 | "views": { 54 | "postcode": [ 55 | { 56 | "id": "postcode.request", 57 | "name": "Request" 58 | } 59 | ] 60 | }, 61 | "viewsWelcome": [ 62 | { 63 | "view": "postcode.request", 64 | "contents": "[Create Request](command:postcode.createRequest)" 65 | } 66 | ] 67 | }, 68 | "scripts": { 69 | "vscode:prepublish": "yarn run package", 70 | "compile": "cross-env NODE_ENV=development webpack --progress", 71 | "watch": "cross-env NODE_ENV=development webpack --progress --watch", 72 | "package": "cross-env NODE_ENV=production webpack --progress", 73 | "test-compile": "tsc -p ./", 74 | "test-watch": "tsc -watch -p ./", 75 | "pretest": "yarn run test-compile && yarn run lint", 76 | "lint": "eslint src webview --ext .ts,.tsx", 77 | "lint:fix": "eslint --fix src webview --ext .ts,.tsx", 78 | "test": "node ./out/test/runTest.js", 79 | "prepare": "husky install" 80 | }, 81 | "devDependencies": { 82 | "@svgr/webpack": "^5.5.0", 83 | "@types/glob": "^7.1.3", 84 | "@types/mocha": "^8.0.4", 85 | "@types/node": "^12.11.7", 86 | "@types/react": "^17.0.5", 87 | "@types/react-dom": "^17.0.4", 88 | "@types/react-redux": "^7.1.16", 89 | "@types/vscode": "^1.56.0", 90 | "@typescript-eslint/eslint-plugin": "^4.14.1", 91 | "@typescript-eslint/parser": "^4.14.1", 92 | "cross-env": "^7.0.3", 93 | "css-loader": "^5.2.4", 94 | "eslint": "^7.19.0", 95 | "eslint-config-prettier": "^8.3.0", 96 | "eslint-plugin-prettier": "^3.4.0", 97 | "eslint-plugin-react": "^7.23.2", 98 | "file-loader": "^6.2.0", 99 | "glob": "^7.1.6", 100 | "husky": "^7.0.1", 101 | "mini-css-extract-plugin": "^1.6.0", 102 | "mocha": "^8.2.1", 103 | "monaco-editor-webpack-plugin": "^3.1.0", 104 | "prettier": "2.3.0", 105 | "static-site-generator-webpack-plugin": "^3.4.2", 106 | "style-loader": "^2.0.0", 107 | "ts-loader": "^8.0.14", 108 | "typescript": "^4.1.3", 109 | "url-loader": "^4.1.1", 110 | "vscode-test": "^1.5.0", 111 | "webpack": "^5.19.0", 112 | "webpack-cli": "^4.4.0" 113 | }, 114 | "dependencies": { 115 | "@reduxjs/toolkit": "^1.5.1", 116 | "axios": "^0.21.1", 117 | "buffer": "^6.0.3", 118 | "monaco-editor": "^0.24.0", 119 | "path-browserify": "^1.0.1", 120 | "postman-code-generators": "^1.1.5", 121 | "postman-collection": "^3.6.11", 122 | "react": "^17.0.2", 123 | "react-dom": "^17.0.2", 124 | "react-icons": "^4.2.0", 125 | "react-redux": "^7.2.4", 126 | "url": "^0.11.0" 127 | } 128 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from "vscode"; 4 | import * as fs from "fs"; 5 | import axios from "axios"; 6 | import * as https from "https"; 7 | import { RequestOptions } from "../webview/features/requestOptions/requestOptionsSlice"; 8 | 9 | // this method is called when your extension is activated 10 | // your extension is activated the very first time the command is executed 11 | export function activate(context: vscode.ExtensionContext) { 12 | const webviewContent = fs 13 | .readFileSync( 14 | vscode.Uri.joinPath(context.extensionUri, "dist/index.html").fsPath, 15 | { encoding: "utf-8" } 16 | ) 17 | .replace( 18 | "styleUri", 19 | vscode.Uri.joinPath(context.extensionUri, "/dist/main.css") 20 | .with({ scheme: "vscode-resource" }) 21 | .toString() 22 | ) 23 | .replace( 24 | "scriptUri", 25 | vscode.Uri.joinPath(context.extensionUri, "/dist/webview.js") 26 | .with({ scheme: "vscode-resource" }) 27 | .toString() 28 | ); 29 | 30 | // The command has been defined in the package.json file 31 | // Now provide the implementation of the command with registerCommand 32 | // The commandId parameter must match the command field in package.json 33 | const disposable = vscode.commands.registerCommand( 34 | "postcode.createRequest", 35 | () => { 36 | // The code you place here will be executed every time your command is executed 37 | 38 | // Display a message box to the user 39 | vscode.window.showInformationMessage("Welcome to Postcode!"); 40 | 41 | const panel = vscode.window.createWebviewPanel( 42 | "postcode", 43 | "Postcode", 44 | vscode.ViewColumn.One, 45 | { 46 | enableScripts: true, 47 | retainContextWhenHidden: true, 48 | localResourceRoots: [ 49 | vscode.Uri.joinPath(context.extensionUri, "dist"), 50 | ], 51 | } 52 | ); 53 | 54 | panel.webview.html = webviewContent; 55 | panel.iconPath = vscode.Uri.joinPath( 56 | context.extensionUri, 57 | "icons/icon.png" 58 | ); 59 | 60 | panel.webview.onDidReceiveMessage( 61 | ({ method, url, headers, body, auth, options }) => { 62 | // Options Section 63 | const requestOptions = options as RequestOptions; 64 | let requestStartedAt, responseDuration; 65 | 66 | if (!url) { 67 | panel.webview.postMessage({ 68 | type: "response", 69 | error: { message: "Request URL is empty" }, 70 | }); 71 | vscode.window.showInformationMessage("Request URL is empty"); 72 | return; 73 | } 74 | 75 | const headersObj = {}; 76 | 77 | if (auth.type === "bearer") { 78 | headersObj["Authorization"] = `Bearer ${auth.bearer.token}`; 79 | } 80 | 81 | headers.forEach(({ key, value, disabled }) => { 82 | if (!disabled) { 83 | headersObj[key] = value; 84 | } 85 | }); 86 | 87 | let data = ""; 88 | if (body.mode === "formdata") { 89 | const dataObj = new URLSearchParams(); 90 | body.formdata.forEach(({ key, value, disabled }) => { 91 | if (!disabled) { 92 | dataObj.append(key, value); 93 | } 94 | }); 95 | data = dataObj.toString(); 96 | headersObj["Content-Type"] = "multipart/form-data"; 97 | } else if (body.mode === "urlencoded") { 98 | const dataObj = new URLSearchParams(); 99 | body.urlencoded.forEach(({ key, value, disabled }) => { 100 | if (!disabled) { 101 | dataObj.append(key, value); 102 | } 103 | }); 104 | data = dataObj.toString(); 105 | headersObj["Content-Type"] = "application/x-www-form-urlencoded"; 106 | } else if (body.mode === "raw") { 107 | data = body.raw; 108 | headersObj["Content-Type"] = { 109 | json: "application/json", 110 | html: "text/html", 111 | xml: "text/xml", 112 | text: "text/plain", 113 | }[body.options.raw.language]; 114 | } else if (body.mode === "file") { 115 | data = body.fileData; 116 | headersObj["Content-Type"] = "application/octet-stream"; 117 | } else if (body.mode === "graphql") { 118 | data = JSON.stringify({ 119 | query: body.graphql.query, 120 | variables: body.graphql.variables, 121 | }); 122 | headersObj["Content-Type"] = "application/json"; 123 | } 124 | 125 | // Option 1. StrictSSL 126 | https.globalAgent.options.rejectUnauthorized = 127 | requestOptions.strictSSL === "yes"; 128 | 129 | axios.interceptors.request.use((config) => { 130 | requestStartedAt = new Date().getTime(); 131 | return config; 132 | }); 133 | 134 | axios.interceptors.response.use((config) => { 135 | responseDuration = new Date().getTime() - requestStartedAt; 136 | return config; 137 | }); 138 | 139 | axios({ 140 | method, 141 | url, 142 | baseURL: "", 143 | data: data, 144 | headers: headersObj, 145 | auth: auth.type === "basic" ? auth.basic : undefined, 146 | transformResponse: [(data) => data], 147 | responseType: "text", 148 | validateStatus: () => true, 149 | }) 150 | .then((resp) => 151 | panel.webview.postMessage({ 152 | type: "response", 153 | data: resp.data, 154 | status: resp.status, 155 | statusText: resp.statusText, 156 | headers: resp.headers, 157 | duration: responseDuration, 158 | }) 159 | ) 160 | .catch((err) => { 161 | panel.webview.postMessage({ 162 | type: "response", 163 | error: err, 164 | }); 165 | vscode.window.showInformationMessage( 166 | "Error: Could not send request" 167 | ); 168 | }); 169 | } 170 | ); 171 | } 172 | ); 173 | 174 | context.subscriptions.push(disposable); 175 | } 176 | 177 | // this method is called when your extension is deactivated 178 | // eslint-disable-next-line @typescript-eslint/no-empty-function 179 | export function deactivate() {} 180 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { runTests } from "vscode-test"; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index"); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error("Failed to run tests"); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from "vscode"; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite("Extension Test Suite", () => { 9 | vscode.window.showInformationMessage("Start all tests."); 10 | 11 | test("Sample test", () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as Mocha from "mocha"; 3 | import * as glob from "glob"; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: "tdd", 9 | color: true, 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, ".."); 13 | 14 | return new Promise((c, e) => { 15 | glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run((failures) => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "jsx": "react", 7 | "lib": ["es2019", "dom"], 8 | "sourceMap": true, 9 | "rootDir": ".", 10 | "resolveJsonModule": true, 11 | "strict": false /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": ["node_modules", ".vscode-test"] 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | //@ts-check 3 | 4 | "use strict"; 5 | 6 | const path = require("path"); 7 | const webpack = require("webpack"); 8 | const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); 9 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 10 | const StaticSiteGeneratorPlugin = require("static-site-generator-webpack-plugin"); 11 | 12 | const imageInlineSizeLimit = parseInt( 13 | process.env.IMAGE_INLINE_SIZE_LIMIT || "10000" 14 | ); 15 | 16 | const baseConfig = (webpackEnv) => { 17 | const isEnvDevelopment = webpackEnv === "development"; 18 | const isEnvProduction = webpackEnv === "production"; 19 | 20 | return { 21 | mode: isEnvProduction ? "production" : isEnvDevelopment && "development", 22 | bail: isEnvProduction, 23 | devtool: isEnvProduction 24 | ? "source-map" 25 | : isEnvDevelopment && "eval-cheap-module-source-map", 26 | resolve: { 27 | fallback: { 28 | buffer: require.resolve("buffer"), 29 | path: require.resolve("path-browserify"), 30 | url: require.resolve("url"), 31 | }, 32 | extensions: [".ts", ".tsx", ".js"], 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | oneOf: [ 38 | { 39 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], 40 | loader: require.resolve("url-loader"), 41 | options: { 42 | limit: imageInlineSizeLimit, 43 | name: "static/media/[name].[hash:8].[ext]", 44 | }, 45 | }, 46 | { 47 | test: /\.svg$/, 48 | use: [ 49 | require.resolve("@svgr/webpack"), 50 | require.resolve("url-loader"), 51 | ], 52 | }, 53 | { 54 | test: /\.tsx?$/, 55 | exclude: /node_modules/, 56 | loader: require.resolve("ts-loader"), 57 | }, 58 | { 59 | test: /\.css$/, 60 | use: [ 61 | MiniCssExtractPlugin.loader, 62 | { 63 | loader: require.resolve("css-loader"), 64 | options: { 65 | importLoaders: 1, 66 | sourceMap: isEnvProduction || isEnvDevelopment, 67 | }, 68 | }, 69 | ], 70 | sideEffects: true, 71 | }, 72 | { 73 | loader: require.resolve("file-loader"), 74 | exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], 75 | options: { 76 | name: "media/[name].[hash:8].[ext]", 77 | }, 78 | }, 79 | ], 80 | }, 81 | ], 82 | }, 83 | plugins: [ 84 | new MiniCssExtractPlugin({ 85 | filename: "ignore.css", 86 | }), 87 | ], 88 | }; 89 | }; 90 | 91 | const extensionConfig = (webpackEnv) => { 92 | return { 93 | ...baseConfig(webpackEnv), 94 | target: "node", 95 | entry: "./src/extension.ts", 96 | output: { 97 | path: path.resolve(__dirname, "dist"), 98 | filename: "extension.js", 99 | libraryTarget: "commonjs2", 100 | }, 101 | externals: { vscode: "commonjs vscode" }, 102 | }; 103 | }; 104 | 105 | const webviewConfig = (webpackEnv) => { 106 | return { 107 | ...baseConfig(webpackEnv), 108 | entry: "./webview/index.tsx", 109 | output: { 110 | path: path.resolve(__dirname, "dist"), 111 | filename: "webview.js", 112 | }, 113 | plugins: [ 114 | new MiniCssExtractPlugin(), 115 | new MonacoWebpackPlugin({ 116 | languages: [ 117 | "csharp", 118 | "dart", 119 | "go", 120 | "graphql", 121 | "html", 122 | "java", 123 | "typescript", 124 | "json", 125 | "objective-c", 126 | "php", 127 | "powershell", 128 | "python", 129 | "ruby", 130 | "shell", 131 | "swift", 132 | "xml", 133 | ], 134 | }), 135 | new webpack.ProvidePlugin({ 136 | Buffer: ["buffer", "Buffer"], 137 | process: "process/browser", 138 | }), 139 | ], 140 | }; 141 | }; 142 | 143 | const prerenderConfig = (webpackEnv) => { 144 | const config = baseConfig(webpackEnv); 145 | 146 | return { 147 | ...config, 148 | target: "node", 149 | entry: "./webview/prerender.tsx", 150 | output: { 151 | path: path.resolve(__dirname, "dist"), 152 | filename: "prerender.js", 153 | libraryTarget: "commonjs2", 154 | }, 155 | plugins: [ 156 | new MiniCssExtractPlugin({ 157 | filename: "ignore.css", 158 | }), 159 | new StaticSiteGeneratorPlugin({ 160 | paths: ["/"], 161 | }), 162 | new webpack.ProvidePlugin({ 163 | Buffer: ["buffer", "Buffer"], 164 | process: "process/browser", 165 | }), 166 | ], 167 | }; 168 | }; 169 | 170 | module.exports = [extensionConfig, webviewConfig, prerenderConfig]; 171 | -------------------------------------------------------------------------------- /webview/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | background-color: var(--background); 3 | } 4 | -------------------------------------------------------------------------------- /webview/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./App.css"; 3 | import { responseUpdated } from "./features/response/responseSlice"; 4 | import { Postcode } from "./pages/Postcode"; 5 | import { useAppDispatch } from "./redux/hooks"; 6 | 7 | const App = () => { 8 | const dispatch = useAppDispatch(); 9 | 10 | React.useEffect(() => { 11 | window.addEventListener("message", (event) => { 12 | if (event.data.type === "response") { 13 | dispatch(responseUpdated(event.data)); 14 | } 15 | }); 16 | }, []); 17 | 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /webview/acquireVsCodeApi.d.ts: -------------------------------------------------------------------------------- 1 | declare var acquireVsCodeApi: any; 2 | -------------------------------------------------------------------------------- /webview/components/RequestBar/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Url } from "postman-collection"; 3 | import vscode from "../../vscode"; 4 | import { RequestMethodSelector } from "../../features/requestMethod/RequestMethodSelector"; 5 | import { RequestUrl } from "../../features/requestUrl/RequestUrl"; 6 | import { responseLoadingStarted } from "../../features/response/responseSlice"; 7 | import { selectRequestAuth } from "../../features/requestAuth/requestAuthSlice"; 8 | import { selectRequestBody } from "../../features/requestBody/requestBodySlice"; 9 | import { selectRequestHeaders } from "../../features/requestHeader/requestHeaderSlice"; 10 | import { selectRequestUrl } from "../../features/requestUrl/requestUrlSlice"; 11 | import { selectRequestMethod } from "../../features/requestMethod/requestMethodSlice"; 12 | import { selectRequestOptions } from "../../features/requestOptions/requestOptionsSlice"; 13 | import { useAppDispatch, useAppSelector } from "../../redux/hooks"; 14 | import "./styles.css"; 15 | 16 | export const RequestBar = () => { 17 | const dispatch = useAppDispatch(); 18 | 19 | const requestMethod = useAppSelector(selectRequestMethod); 20 | const requestHeaders = useAppSelector(selectRequestHeaders); 21 | const requestBody = useAppSelector(selectRequestBody); 22 | const requestUrl = useAppSelector(selectRequestUrl); 23 | const requestAuth = useAppSelector(selectRequestAuth); 24 | const requestOptions = useAppSelector(selectRequestOptions); 25 | 26 | return ( 27 |
{ 30 | dispatch(responseLoadingStarted()); 31 | const { protocol } = Url.parse(requestUrl); 32 | vscode.postMessage({ 33 | method: requestMethod, 34 | auth: requestAuth, 35 | body: requestBody, 36 | headers: requestHeaders, 37 | url: protocol ? requestUrl : `http://${requestUrl}`, 38 | options: requestOptions, 39 | }); 40 | e.preventDefault(); 41 | }} 42 | > 43 | 44 | 45 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /webview/components/RequestBar/styles.css: -------------------------------------------------------------------------------- 1 | .request-bar { 2 | display: flex; 3 | padding: 0 20px; 4 | padding-top: 30px; 5 | } 6 | 7 | .input-request-url { 8 | flex: 2; 9 | padding: 13px 20px; 10 | color: var(--default-text); 11 | display: inline-block; 12 | outline: none; 13 | border: 0; 14 | border-radius: 0 2px 2px 0; 15 | background-color: var(--input-field); 16 | font-size: var(--default-font-size); 17 | } 18 | .input-request-url:focus { 19 | outline: none; 20 | } 21 | .input-request-url:hover { 22 | background-color: var(--input-field-hover); 23 | } 24 | 25 | .button-request-send { 26 | background-color: var(--send-button); 27 | color: #fff; 28 | font-weight: bold; 29 | padding: 13px 20px; 30 | margin: 0 0 0 12px; 31 | display: inline-block; 32 | outline: none; 33 | border-radius: 5%; 34 | border: 0; 35 | cursor: pointer; 36 | font-size: var(--default-font-size); 37 | } 38 | .button-request-send:hover { 39 | background-color: var(--send-button-hover); 40 | } 41 | -------------------------------------------------------------------------------- /webview/components/RequestOptionsBar/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import * as propTypes from "prop-types"; 4 | import { requestOptions } from "../../constants/request-options"; 5 | import { useAppSelector } from "../../redux/hooks"; 6 | import { selectRequestHeaders } from "../../features/requestHeader/requestHeaderSlice"; 7 | 8 | export const RequestOptionsTab = (props) => { 9 | const { selected, setSelected } = props; 10 | const header = useAppSelector(selectRequestHeaders); 11 | 12 | return ( 13 |
14 |
15 | {requestOptions.map((option) => ( 16 | 38 | ))} 39 |
40 | 48 |
49 | ); 50 | }; 51 | 52 | RequestOptionsTab.propTypes = { 53 | selected: propTypes.string.isRequired, 54 | setSelected: propTypes.func.isRequired, 55 | }; 56 | -------------------------------------------------------------------------------- /webview/components/RequestOptionsBar/styles.css: -------------------------------------------------------------------------------- 1 | .request-options-tab-wrapper { 2 | display: flex; 3 | justify-content: space-between; 4 | padding: 15px 20px 0 20px; 5 | } 6 | 7 | .request-options { 8 | display: flex; 9 | } 10 | 11 | .request-option { 12 | padding: 0px 5px 10px 5px; 13 | margin: 0 10px 0 0; 14 | display: flex; 15 | cursor: pointer; 16 | background-color: transparent; 17 | color: var(--default-text-light); 18 | outline: none; 19 | border: 3px solid transparent; 20 | border-radius: 0; 21 | font-weight: 500; 22 | font-size: var(--default-font-size); 23 | } 24 | .request-option:hover { 25 | color: var(--default-text); 26 | } 27 | 28 | .request-option-selected { 29 | color: var(--default-text); 30 | border-bottom: 3px solid var(--primary); 31 | } 32 | 33 | .request-options-header-length { 34 | margin-left: 4px; 35 | color: var(--tab-info); 36 | } 37 | 38 | .button-request { 39 | display: inline-block; 40 | cursor: pointer; 41 | background-color: transparent; 42 | color: var(--primary); 43 | outline: none; 44 | border: 3px solid transparent; 45 | border-radius: 0; 46 | font-weight: 500; 47 | font-size: var(--default-font-size); 48 | } 49 | 50 | .button-request:hover { 51 | color: var(--primary-dark); 52 | } 53 | 54 | .hidden { 55 | display: none; 56 | } -------------------------------------------------------------------------------- /webview/components/RequestOptionsWindow/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { RequestQueryParams } from "../../features/requestUrl/RequestQueryParams"; 3 | import { RequestAuth } from "../../features/requestAuth/RequestAuth"; 4 | import { Body } from "../../features/requestBody/RequestBody"; 5 | import { Headers } from "../../features/requestHeader/HeadersWindow"; 6 | import { CodeSnippet } from "../../features/codeGen/CodeSnippet"; 7 | import { RequestOptions } from "../../features/requestOptions/RequestOptions"; 8 | import * as propTypes from "prop-types"; 9 | import "./styles.css"; 10 | 11 | export const RequestOptionsWindow = (props) => { 12 | const { selected } = props; 13 | return ( 14 |
15 | {selected === "params" ? ( 16 | 17 | ) : selected === "authorization" ? ( 18 | 19 | ) : selected === "body" ? ( 20 | 21 | ) : selected === "headers" ? ( 22 | 23 | ) : selected === "code" ? ( 24 | 25 | ) : selected === "options" ? ( 26 | 27 | ) : null} 28 |
29 | ); 30 | }; 31 | 32 | RequestOptionsWindow.propTypes = { 33 | selected: propTypes.string.isRequired, 34 | }; 35 | -------------------------------------------------------------------------------- /webview/components/RequestOptionsWindow/styles.css: -------------------------------------------------------------------------------- 1 | .request-options-window-wrapper { 2 | flex: 1; 3 | padding: 5px 20px 10px 20px; 4 | overflow: scroll; 5 | } 6 | -------------------------------------------------------------------------------- /webview/components/Response/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ResponseTab } from "../../features/response/ResponseTab"; 3 | import { ResponseWindow } from "../../features/response/ResponseWindow"; 4 | import { ReactComponent as PackageIcon } from "../../icons/package.svg"; 5 | import "./styles.css"; 6 | import { useAppSelector } from "../../redux/hooks"; 7 | import { selectResponse } from "../../features/response/responseSlice"; 8 | import { supportedLangs } from "../../constants/supported-langs"; 9 | 10 | export const Response = () => { 11 | const response = useAppSelector(selectResponse); 12 | const [selected, setSelected] = React.useState("body"); 13 | const [language, setLanguage] = React.useState(supportedLangs[0].value); 14 | 15 | if (response.loading) { 16 | return ( 17 |
18 |
Sending request ...
19 |
20 |
21 | ); 22 | } else if (response.initial) { 23 | return ( 24 |
25 |
Hit Send to get a response
26 | 27 |
28 | ); 29 | } else if (response.error) { 30 | return ( 31 |
32 |
Could not send request
33 |
{`Error: ${response.error.message}`}
34 | 35 |
36 | ); 37 | } else { 38 | return ( 39 |
40 | 46 | 47 |
48 | ); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /webview/components/Response/styles.css: -------------------------------------------------------------------------------- 1 | .response-body-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | font-size: var(--default-font-size); 6 | border-top: var(--default-border-size) solid var(--border); 7 | } 8 | 9 | .error-response-wrapper { 10 | display: flex; 11 | flex-direction: column; 12 | justify-content: center; 13 | align-items: center; 14 | font-size: var(--default-font-size); 15 | border-top: var(--default-border-size) solid var(--border); 16 | } 17 | 18 | .img-error-response { 19 | margin: 15px; 20 | fill: var(--logo-color); 21 | width: 20%; 22 | height: 20%; 23 | } 24 | 25 | .error-message { 26 | margin: 10px; 27 | color: var(--error-message); 28 | } 29 | 30 | .initial-response-wrapper { 31 | border-top: var(--default-border-size) solid var(--border); 32 | display: flex; 33 | flex-direction: column; 34 | justify-content: center; 35 | align-items: center; 36 | color: var(--default-text-light); 37 | font-size: var(--default-font-size); 38 | flex: 1; 39 | } 40 | 41 | .img-initial-response { 42 | fill: var(--logo-color); 43 | width: 20%; 44 | height: 20%; 45 | } 46 | 47 | .initial-text { 48 | padding: 10px; 49 | } 50 | 51 | .loader-wrapper { 52 | border-top: var(--default-border-size) solid var(--border); 53 | display: flex; 54 | flex: 1; 55 | flex-direction: column; 56 | justify-content: center; 57 | align-items: center; 58 | font-size: var(--default-font-size); 59 | } 60 | 61 | .loader, 62 | .loader:before, 63 | .loader:after { 64 | border-radius: 50%; 65 | width: 20px; 66 | height: 20px; 67 | -webkit-animation-fill-mode: both; 68 | animation-fill-mode: both; 69 | -webkit-animation: load7 1.8s infinite ease-in-out; 70 | animation: load7 1.8s infinite ease-in-out; 71 | } 72 | .loader { 73 | color: var(--border); 74 | position: relative; 75 | text-indent: -9999em; 76 | -webkit-transform: translateZ(0); 77 | -ms-transform: translateZ(0); 78 | transform: translateZ(0); 79 | -webkit-animation-delay: -0.16s; 80 | animation-delay: -0.16s; 81 | } 82 | .loader:before, 83 | .loader:after { 84 | content: ""; 85 | position: absolute; 86 | top: 0; 87 | } 88 | .loader:before { 89 | left: -3.5em; 90 | -webkit-animation-delay: -0.32s; 91 | animation-delay: -0.32s; 92 | } 93 | .loader:after { 94 | left: 3.5em; 95 | } 96 | @-webkit-keyframes load7 { 97 | 0%, 98 | 80%, 99 | 100% { 100 | box-shadow: 0 2.5em 0 -1.3em; 101 | } 102 | 40% { 103 | box-shadow: 0 2.5em 0 0; 104 | } 105 | } 106 | @keyframes load7 { 107 | 0%, 108 | 80%, 109 | 100% { 110 | box-shadow: 0 2.5em 0 -1.3em; 111 | } 112 | 40% { 113 | box-shadow: 0 2.5em 0 0; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /webview/constants/request-options.ts: -------------------------------------------------------------------------------- 1 | export const requestOptions = [ 2 | { 3 | name: "Params", 4 | value: "params", 5 | }, 6 | { 7 | name: "Authorization", 8 | value: "authorization", 9 | }, 10 | { 11 | name: "Headers", 12 | value: "headers", 13 | }, 14 | { 15 | name: "Body", 16 | value: "body", 17 | }, 18 | { 19 | name: "Options", 20 | value: "options", 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /webview/constants/response-options.ts: -------------------------------------------------------------------------------- 1 | export const responseOptions = [ 2 | { 3 | name: "Body", 4 | value: "body", 5 | }, 6 | { 7 | name: "Headers", 8 | value: "headers", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /webview/constants/response-views.ts: -------------------------------------------------------------------------------- 1 | export const responseViews = [ 2 | { 3 | name: "Pretty", 4 | value: "pretty", 5 | }, 6 | { 7 | name: "Raw", 8 | value: "raw", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /webview/constants/supported-langs.ts: -------------------------------------------------------------------------------- 1 | export const supportedLangs = [ 2 | { 3 | name: "JSON", 4 | value: "json", 5 | }, 6 | { 7 | name: "HTML", 8 | value: "html", 9 | }, 10 | { 11 | name: "XML", 12 | value: "xml", 13 | }, 14 | { 15 | name: "Text", 16 | value: "text", 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /webview/features/codeGen/CodeSnippet/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { 4 | codeGenLanguageUpdated, 5 | codeGenOptions, 6 | codeGenVariantUpdated, 7 | selectCodeGenEditorLanguage, 8 | selectCodeGenLanguage, 9 | selectCodeGenLanguageKey, 10 | selectCodeGenVariant, 11 | selectRequest, 12 | } from "../codeGenSlice"; 13 | import * as codegen from "postman-code-generators"; 14 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 15 | 16 | const Editor = React.lazy(() => import("../../../shared/Editor")); 17 | 18 | export const CodeSnippet = () => { 19 | const [code, setCode] = React.useState(""); 20 | 21 | const language = useAppSelector(selectCodeGenLanguage); 22 | const variant = useAppSelector(selectCodeGenVariant); 23 | const editorLanguage = useAppSelector(selectCodeGenEditorLanguage); 24 | const languageKey = useAppSelector(selectCodeGenLanguageKey); 25 | const dispatch = useAppDispatch(); 26 | 27 | const request = useAppSelector(selectRequest); 28 | 29 | React.useEffect(() => { 30 | codegen.convert(languageKey, variant, request, {}, (err, snippet) => { 31 | setCode(snippet); 32 | }); 33 | }, [languageKey, variant, request]); 34 | 35 | return ( 36 |
37 |
38 | 49 | 62 |
63 |
64 | loading
}> 65 | 72 | 73 |
74 |
75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /webview/features/codeGen/CodeSnippet/styles.css: -------------------------------------------------------------------------------- 1 | .code-gen-wrapper { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .code-gen-options { 8 | display: flex; 9 | align-items: center; 10 | } 11 | 12 | .code-display { 13 | height: 95%; 14 | border-top: var(--default-border-size) solid var(--border); 15 | margin-top: 15px; 16 | } 17 | 18 | .code-gen-editor { 19 | height: 95%; 20 | } 21 | 22 | .select-code-option { 23 | padding: 4px; 24 | margin-right: 8px; 25 | display: inline-block; 26 | font-weight: 600; 27 | outline: none; 28 | border: var(--default-border-size) solid var(--border); 29 | border-radius: 2px; 30 | background-color: var(--background); 31 | color: var(--default-text-light); 32 | cursor: pointer; 33 | font-size: var(--default-font-size); 34 | } 35 | .select-code-option:hover { 36 | color: var(--default-text); 37 | } 38 | .select-code-option:focus { 39 | outline: none; 40 | } 41 | -------------------------------------------------------------------------------- /webview/features/codeGen/codeGenSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | import { Request } from "postman-collection"; 4 | 5 | export const codeGenOptions = [ 6 | { language: "C", variants: ["libcurl"], editor: "c", key: "c" }, 7 | { language: "C#", variants: ["RestSharp"], editor: "csharp", key: "csharp" }, 8 | { language: "cURL", variants: ["cURL"], editor: "shell", key: "curl" }, 9 | { language: "Dart", variants: ["http"], editor: "dart", key: "dart" }, 10 | { language: "Go", variants: ["Native"], editor: "go", key: "go" }, 11 | { language: "HTTP", variants: ["HTTP"], editor: "text", key: "http" }, 12 | { language: "Java", variants: ["OkHttp", "Unirest"], editor: "java" }, 13 | { 14 | language: "JavaScript", 15 | variants: ["Fetch", "jQuery", "XHR"], 16 | editor: "javascript", 17 | key: "javascript", 18 | }, 19 | { 20 | language: "NodeJs", 21 | variants: ["Axios", "Native", "Request", "Unirest"], 22 | editor: "javascript", 23 | key: "nodejs", 24 | }, 25 | { 26 | language: "Objective-C", 27 | variants: ["NSURLSession"], 28 | editor: "objective-c", 29 | key: "objective-c", 30 | }, 31 | { language: "OCaml", variants: ["Cohttp"], editor: "text", key: "ocaml" }, 32 | { 33 | language: "PHP", 34 | variants: ["cURL", "pecl_http", "HTTP_Request2"], 35 | editor: "php", 36 | key: "php", 37 | }, 38 | { 39 | language: "PowerShell", 40 | variants: ["RestMethod"], 41 | editor: "powershell", 42 | key: "powershell", 43 | }, 44 | { 45 | language: "Python", 46 | variants: ["http.client", "Requests"], 47 | editor: "python", 48 | key: "python", 49 | }, 50 | { language: "Ruby", variants: ["Net:HTTP"], editor: "ruby", key: "ruby" }, 51 | { 52 | language: "Shell", 53 | variants: ["Httpie", "wget"], 54 | editor: "shell", 55 | key: "shell", 56 | }, 57 | { 58 | language: "Swift", 59 | variants: ["URLSession"], 60 | editor: "swift", 61 | key: "swift", 62 | }, 63 | ]; 64 | 65 | export interface codeGenOptionState { 66 | language: string; 67 | variant: string; 68 | } 69 | 70 | const initialState = { language: "C", variant: "libcurl" }; 71 | 72 | const codeGenSlice = createSlice({ 73 | name: "codeGenOptions", 74 | initialState, 75 | reducers: { 76 | codeGenLanguageUpdated(state, action: PayloadAction) { 77 | state.language = action.payload; 78 | state.variant = codeGenOptions.filter( 79 | ({ language }) => language === action.payload 80 | )[0].variants[0]; 81 | }, 82 | codeGenVariantUpdated(state, action: PayloadAction) { 83 | state.variant = action.payload; 84 | }, 85 | }, 86 | }); 87 | 88 | export const { codeGenLanguageUpdated, codeGenVariantUpdated } = 89 | codeGenSlice.actions; 90 | 91 | export const selectCodeGenLanguage = (state: RootState) => 92 | state.codeGenOptions.language; 93 | export const selectCodeGenVariant = (state: RootState) => 94 | state.codeGenOptions.variant; 95 | export const selectCodeGenEditorLanguage = (state: RootState) => 96 | codeGenOptions.filter( 97 | ({ language }) => language === state.codeGenOptions.language 98 | )[0].editor; 99 | export const selectCodeGenLanguageKey = (state: RootState) => 100 | codeGenOptions.filter( 101 | ({ language }) => language === state.codeGenOptions.language 102 | )[0].key; 103 | 104 | export const selectRequest = (state: RootState) => 105 | new Request({ 106 | method: state.requestMethod, 107 | url: state.requestUrl, 108 | header: state.requestHeader, 109 | body: { 110 | mode: state.requestBody.mode, 111 | options: state.requestBody.options, 112 | [state.requestBody.mode]: state.requestBody[state.requestBody.mode], 113 | }, 114 | auth: { 115 | type: state.requestAuth.type, 116 | [state.requestAuth.type]: state.requestAuth[state.requestAuth.type], 117 | }, 118 | }); 119 | 120 | export default codeGenSlice.reducer; 121 | -------------------------------------------------------------------------------- /webview/features/requestAuth/BasicAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 4 | import { 5 | requestAuthOptionsUpdated, 6 | selectBasicAuthOptions, 7 | } from "../requestAuthSlice"; 8 | 9 | export const BasicAuth = () => { 10 | const basicAuthOptions = useAppSelector(selectBasicAuthOptions); 11 | const dispatch = useAppDispatch(); 12 | 13 | return ( 14 |
15 |
16 |
Username
17 | 26 | dispatch( 27 | requestAuthOptionsUpdated({ 28 | username: e.target.value, 29 | password: basicAuthOptions.password, 30 | }) 31 | ) 32 | } 33 | /> 34 |
35 |
36 |
Password
37 | 46 | dispatch( 47 | requestAuthOptionsUpdated({ 48 | username: basicAuthOptions.username, 49 | password: e.target.value, 50 | }) 51 | ) 52 | } 53 | /> 54 |
55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /webview/features/requestAuth/BasicAuth/styles.css: -------------------------------------------------------------------------------- 1 | .basic-auth-input-group { 2 | padding: 15px 0 0 0; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .label-basic-auth { 9 | font-size: var(--default-font-size); 10 | color: var(--default-text); 11 | padding-right: 20px; 12 | } 13 | 14 | .input-basic-auth { 15 | width: 60%; 16 | padding: 13px 20px; 17 | color: var(--default-text); 18 | display: inline-block; 19 | outline: none; 20 | border: 0; 21 | border-radius: 0 2px 2px 0; 22 | background-color: var(--input-field); 23 | font-size: var(--default-font-size); 24 | } 25 | .input-basic-auth:focus { 26 | outline: none; 27 | } 28 | .input-basic-auth:hover { 29 | background-color: var(--input-field-hover); 30 | } 31 | -------------------------------------------------------------------------------- /webview/features/requestAuth/BearerToken/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 3 | import { 4 | requestAuthOptionsUpdated, 5 | selectBearerAuthOptions, 6 | } from "../requestAuthSlice"; 7 | import "./styles.css"; 8 | 9 | export const BearerToken = () => { 10 | const bearerAuthOptions = useAppSelector(selectBearerAuthOptions); 11 | const dispatch = useAppDispatch(); 12 | 13 | return ( 14 |
15 |
Token
16 | 25 | dispatch(requestAuthOptionsUpdated({ token: e.target.value })) 26 | } 27 | /> 28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /webview/features/requestAuth/BearerToken/styles.css: -------------------------------------------------------------------------------- 1 | .bearer-token-wrapper { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | padding: 15px 0; 6 | } 7 | 8 | .label-bearer-token { 9 | font-size: var(--default-font-size); 10 | color: var(--default-text); 11 | padding-right: 20px; 12 | } 13 | 14 | .input-bearer-token { 15 | width: 60%; 16 | padding: 13px 20px; 17 | color: var(--default-text); 18 | display: inline-block; 19 | outline: none; 20 | border: 0; 21 | border-radius: 0 2px 2px 0; 22 | background-color: var(--input-field); 23 | font-size: var(--default-font-size); 24 | } 25 | .input-bearer-token:focus { 26 | outline: none; 27 | } 28 | .input-bearer-token:hover { 29 | background-color: var(--input-field-hover); 30 | } 31 | -------------------------------------------------------------------------------- /webview/features/requestAuth/NoAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | 4 | export const NoAuth = () => { 5 | return ( 6 |
7 |
8 | This request does not use any authorization. 9 |
10 |
11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /webview/features/requestAuth/NoAuth/styles.css: -------------------------------------------------------------------------------- 1 | .no-auth-wrapper { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | -------------------------------------------------------------------------------- /webview/features/requestAuth/RequestAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { NoAuth } from "../NoAuth"; 4 | import { BearerToken } from "../BearerToken"; 5 | import { BasicAuth } from "../BasicAuth"; 6 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 7 | import { 8 | requestAuthTypes, 9 | requestAuthTypeUpdated, 10 | selectRequestAuthType, 11 | } from "../requestAuthSlice"; 12 | 13 | export const RequestAuth = () => { 14 | const requestAuthType = useAppSelector(selectRequestAuthType); 15 | const dispatch = useAppDispatch(); 16 | 17 | return ( 18 |
19 |
20 |
Authorization Type:
21 | 32 |
33 |
34 | {requestAuthType === "noauth" ? ( 35 | 36 | ) : requestAuthType === "bearer" ? ( 37 | 38 | ) : requestAuthType === "basic" ? ( 39 | 40 | ) : null} 41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /webview/features/requestAuth/RequestAuth/styles.css: -------------------------------------------------------------------------------- 1 | .req-auth-wrapper { 2 | margin-top: 15px; 3 | } 4 | 5 | .auth-type { 6 | display: flex; 7 | align-items: center; 8 | } 9 | 10 | .label-auth-type { 11 | font-size: var(--default-font-size); 12 | color: var(--default-text); 13 | padding-right: 12px; 14 | } 15 | 16 | .select-auth-type { 17 | padding: 4px; 18 | display: inline-block; 19 | font-weight: 600; 20 | outline: none; 21 | border: var(--default-border-size) solid var(--border); 22 | border-radius: 2px; 23 | background-color: var(--background); 24 | color: var(--default-text-light); 25 | cursor: pointer; 26 | font-size: var(--default-font-size); 27 | } 28 | .select-auth-type:hover { 29 | color: var(--default-text); 30 | } 31 | .select-auth-type:focus { 32 | outline: none; 33 | } 34 | 35 | .req-auth-type-window { 36 | border-top: var(--default-border-size) solid var(--border); 37 | margin-top: 15px; 38 | } 39 | -------------------------------------------------------------------------------- /webview/features/requestAuth/requestAuthSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | 4 | export const requestAuthTypes = [ 5 | { name: "No Auth", value: "noauth" }, 6 | { name: "Bearer Token", value: "bearer" }, 7 | { name: "Basic Auth", value: "basic" }, 8 | ]; 9 | 10 | export interface BearerAuthOptions { 11 | token: string; 12 | } 13 | 14 | export interface BasicAuthOptions { 15 | username: string; 16 | password: string; 17 | } 18 | 19 | export interface RequestAuthState { 20 | type: string; 21 | bearer: BearerAuthOptions; 22 | basic: BasicAuthOptions; 23 | } 24 | 25 | const initialState: RequestAuthState = { 26 | type: "noauth", 27 | bearer: { token: "" }, 28 | basic: { username: "", password: "" }, 29 | }; 30 | 31 | const requestAuthSlice = createSlice({ 32 | name: "requestAuth", 33 | initialState, 34 | reducers: { 35 | requestAuthTypeUpdated(state, action: PayloadAction) { 36 | state.type = action.payload; 37 | }, 38 | requestAuthOptionsUpdated( 39 | state, 40 | action: PayloadAction 41 | ) { 42 | state[state.type] = action.payload; 43 | }, 44 | }, 45 | }); 46 | 47 | export const { requestAuthTypeUpdated, requestAuthOptionsUpdated } = 48 | requestAuthSlice.actions; 49 | 50 | export const selectRequestAuth = (state: RootState) => state.requestAuth; 51 | export const selectRequestAuthType = (state: RootState) => 52 | state.requestAuth.type; 53 | export const selectBearerAuthOptions = (state: RootState) => 54 | state.requestAuth.bearer; 55 | export const selectBasicAuthOptions = (state: RootState) => 56 | state.requestAuth.basic; 57 | 58 | export default requestAuthSlice.reducer; 59 | -------------------------------------------------------------------------------- /webview/features/requestBody/Binary/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 4 | import { 5 | requestBodyBinaryUpdated, 6 | selectRequestBodyFile, 7 | } from "../requestBodySlice"; 8 | 9 | export const Binary = () => { 10 | const binary = useAppSelector(selectRequestBodyFile); 11 | const dispatch = useAppDispatch(); 12 | 13 | return ( 14 |
15 |
16 | { 22 | if (e.target.files.length) { 23 | e.target.files[0].text().then((data) => 24 | dispatch( 25 | requestBodyBinaryUpdated({ 26 | name: e.target.files[0].name, 27 | data, 28 | }) 29 | ) 30 | ); 31 | } 32 | }} 33 | > 34 | 37 |
{binary}
38 |
39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /webview/features/requestBody/Binary/styles.css: -------------------------------------------------------------------------------- 1 | .binary-wrapper { 2 | border-top: var(--default-border-size) solid var(--border); 3 | display: flex; 4 | margin-top: 15px; 5 | color: var(--default-text-light); 6 | font-size: var(--default-font-size); 7 | } 8 | 9 | .input-file-wrapper { 10 | margin-top: 20px; 11 | display: flex; 12 | align-items: center; 13 | } 14 | 15 | .input-file-upload { 16 | display: none; 17 | } 18 | 19 | .label-file-upload { 20 | padding: 6px; 21 | cursor: pointer; 22 | background-color: var(--input-field); 23 | margin-right: 10px; 24 | } 25 | -------------------------------------------------------------------------------- /webview/features/requestBody/FormData/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { KeyValueTable } from "../../../shared/KeyValueTable"; 4 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 5 | import { 6 | selectRequestBodyFormData, 7 | requestBodyFormDataItemAdded, 8 | requestBodyFormDataItemDeleted, 9 | requestBodyFormDataItemUpdated, 10 | } from "../requestBodySlice"; 11 | 12 | export const FormData = () => { 13 | const urlEncoded = useAppSelector(selectRequestBodyFormData); 14 | const dispatch = useAppDispatch(); 15 | 16 | return ( 17 |
18 | dispatch(requestBodyFormDataItemAdded(value))} 21 | onRowDelete={(idx) => dispatch(requestBodyFormDataItemDeleted(idx))} 22 | onRowUpdate={(idx, value) => 23 | dispatch(requestBodyFormDataItemUpdated({ idx, value })) 24 | } 25 | /> 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /webview/features/requestBody/FormData/styles.css: -------------------------------------------------------------------------------- 1 | .form-data-wrapper { 2 | margin-top: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /webview/features/requestBody/GraphQL/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 4 | import { 5 | requestBodyGraphqlQueryUpdated, 6 | requestBodyGraphqlVariablesUpdated, 7 | selectRequestBodyGraphqlQuery, 8 | selectRequestBodyGraphqlVariables, 9 | } from "../requestBodySlice"; 10 | 11 | const Editor = React.lazy(() => import("../../../shared/Editor")); 12 | 13 | export const GraphQL = () => { 14 | const query = useAppSelector(selectRequestBodyGraphqlQuery); 15 | const variables = useAppSelector(selectRequestBodyGraphqlVariables); 16 | const dispatch = useAppDispatch(); 17 | 18 | return ( 19 |
20 |
21 |
QUERY
22 | loading
}> 23 | dispatch(requestBodyGraphqlQueryUpdated(data))} 28 | /> 29 | 30 |
31 |
32 |
VARIABLES
33 | loading
}> 34 | 39 | dispatch(requestBodyGraphqlVariablesUpdated(data)) 40 | } 41 | /> 42 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /webview/features/requestBody/GraphQL/styles.css: -------------------------------------------------------------------------------- 1 | .gql-wrapper { 2 | display: flex; 3 | height: 100%; 4 | margin-top: 10px; 5 | } 6 | 7 | .gql-section { 8 | height: 95%; 9 | padding: 0 10px 0 10px; 10 | display: flex; 11 | flex-direction: column; 12 | flex: 1; 13 | } 14 | 15 | .gql-section-heading { 16 | font-size: var(--default-font-size); 17 | padding-bottom: 10px; 18 | font-weight: 700; 19 | } 20 | 21 | .gql-editor { 22 | height: 90%; 23 | } 24 | -------------------------------------------------------------------------------- /webview/features/requestBody/NoBody/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | 4 | export const None = () => { 5 | return ( 6 |
7 |
This request does not have a body
8 |
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /webview/features/requestBody/NoBody/styles.css: -------------------------------------------------------------------------------- 1 | .none-wrapper { 2 | border-top: var(--default-border-size) solid var(--border); 3 | display: flex; 4 | justify-content: center; 5 | margin-top: 15px; 6 | } 7 | 8 | .none-prompt { 9 | color: var(--default-text-light); 10 | font-size: var(--default-font-size); 11 | margin-top: 20px; 12 | } 13 | -------------------------------------------------------------------------------- /webview/features/requestBody/Raw/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 4 | import { 5 | requestBodyRawUpdated, 6 | selectRequestBodyRaw, 7 | selectRequestBodyRawFormat, 8 | selectRequestBodyRawLanguage, 9 | } from "../requestBodySlice"; 10 | 11 | const Editor = React.lazy(() => import("../../../shared/Editor")); 12 | 13 | export const Raw = () => { 14 | const raw = useAppSelector(selectRequestBodyRaw); 15 | const language = useAppSelector(selectRequestBodyRawLanguage); 16 | const format = useAppSelector(selectRequestBodyRawFormat); 17 | const dispatch = useAppDispatch(); 18 | 19 | return ( 20 |
21 | loading
}> 22 | dispatch(requestBodyRawUpdated(data))} 28 | /> 29 | 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /webview/features/requestBody/Raw/styles.css: -------------------------------------------------------------------------------- 1 | .raw-wrapper { 2 | height: 95%; 3 | margin-top: 15px; 4 | } 5 | 6 | .raw-editor { 7 | height: 95%; 8 | } 9 | -------------------------------------------------------------------------------- /webview/features/requestBody/RequestBody/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Binary } from "../Binary"; 3 | import { FormData } from "../FormData"; 4 | import { None } from "../NoBody"; 5 | import { Raw } from "../Raw"; 6 | import { UrlEncoded } from "../UrlEncoded"; 7 | import { GraphQL } from "../GraphQL"; 8 | import { 9 | requestBodyModes, 10 | requestBodyRawLanguages, 11 | requestBodyRawLanguageUpdated, 12 | requestBodyModeUpdated, 13 | selectRequestBodyMode, 14 | selectRequestBodyRawLanguage, 15 | requestBodyRawFormatUpdated, 16 | } from "../requestBodySlice"; 17 | import "./styles.css"; 18 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 19 | 20 | export const Body = () => { 21 | const bodyMode = useAppSelector(selectRequestBodyMode); 22 | const language = useAppSelector(selectRequestBodyRawLanguage); 23 | const hideBeautifyButton = bodyMode === "raw" && language === "json"; 24 | const dispatch = useAppDispatch(); 25 | 26 | return ( 27 |
28 |
29 | {requestBodyModes.map(({ name, value }) => ( 30 |
31 | 37 | e.target.checked && dispatch(requestBodyModeUpdated(value)) 38 | } 39 | className="radio-body-option" 40 | checked={bodyMode === value ? true : false} 41 | /> 42 | 45 |
46 | ))} 47 | {bodyMode === "raw" ? ( 48 | 61 | ) : null} 62 | 74 |
75 |
76 | {bodyMode === "none" ? ( 77 | 78 | ) : bodyMode === "formdata" ? ( 79 | 80 | ) : bodyMode === "urlencoded" ? ( 81 | 82 | ) : bodyMode === "raw" ? ( 83 | 84 | ) : bodyMode === "file" ? ( 85 | 86 | ) : bodyMode === "graphql" ? ( 87 | 88 | ) : null} 89 |
90 |
91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /webview/features/requestBody/RequestBody/styles.css: -------------------------------------------------------------------------------- 1 | .request-body-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .request-body-window-wrapper { 8 | flex: 1; 9 | } 10 | 11 | .body-options-wrapper { 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .body-options { 17 | display: flex; 18 | padding: 6px 5px 6px 0px; 19 | margin: 0 10px 0 0; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .radio-body-option { 25 | cursor: pointer; 26 | outline: none; 27 | border-radius: 0; 28 | margin: 0px 8px; 29 | } 30 | .radio-body-option:checked { 31 | outline: none; 32 | } 33 | .radio-body-option:focus { 34 | outline: none; 35 | } 36 | 37 | .label-body-option { 38 | color: var(--default-text-light); 39 | font-size: var(--default-font-size); 40 | } 41 | 42 | .select-raw-lang { 43 | padding: 4px; 44 | margin-left: 5px; 45 | display: inline-block; 46 | font-weight: 600; 47 | outline: none; 48 | border: var(--default-border-size) solid var(--border); 49 | border-radius: 2px; 50 | background-color: var(--background); 51 | color: var(--default-text-light); 52 | cursor: pointer; 53 | font-size: var(--small-font-size); 54 | } 55 | .select-raw-lang:hover { 56 | color: var(--default-text); 57 | } 58 | .select-raw-lang:focus { 59 | outline: none; 60 | } 61 | 62 | #request-beautify { 63 | margin-left: 10px; 64 | } 65 | -------------------------------------------------------------------------------- /webview/features/requestBody/UrlEncoded/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { KeyValueTable } from "../../../shared/KeyValueTable"; 4 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 5 | import { 6 | selectRequestBodyUrlEncoded, 7 | requestBodyUrlEncodedItemAdded, 8 | requestBodyUrlEncodedItemDeleted, 9 | requestBodyUrlEncodedItemUpdated, 10 | } from "../requestBodySlice"; 11 | 12 | export const UrlEncoded = () => { 13 | const urlEncoded = useAppSelector(selectRequestBodyUrlEncoded); 14 | const dispatch = useAppDispatch(); 15 | 16 | return ( 17 |
18 | dispatch(requestBodyUrlEncodedItemAdded(value))} 21 | onRowDelete={(idx) => dispatch(requestBodyUrlEncodedItemDeleted(idx))} 22 | onRowUpdate={(idx, value) => 23 | dispatch(requestBodyUrlEncodedItemUpdated({ idx, value })) 24 | } 25 | /> 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /webview/features/requestBody/UrlEncoded/styles.css: -------------------------------------------------------------------------------- 1 | .url-encoded-wrapper { 2 | margin-top: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /webview/features/requestBody/requestBodySlice.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 3 | import { RootState } from "../../redux/store"; 4 | 5 | export const requestBodyModes = [ 6 | { name: "none", value: "none" }, 7 | { name: "form-data", value: "formdata" }, 8 | { name: "x-www-form-urlencoded", value: "urlencoded" }, 9 | { name: "raw", value: "raw" }, 10 | { name: "binary", value: "file" }, 11 | { name: "GraphQL", value: "graphql" }, 12 | ]; 13 | 14 | export const requestBodyRawLanguages = [ 15 | { name: "JSON", value: "json" }, 16 | { name: "HTML", value: "html" }, 17 | { name: "XML", value: "xml" }, 18 | { name: "Text", value: "text" }, 19 | ]; 20 | 21 | export interface RequestBodyState { 22 | mode?: string; 23 | disabled: boolean; 24 | raw: string; 25 | file: string; 26 | fileData: string; 27 | formdata: { key: string; value: string; description: string }[]; 28 | urlencoded: { key: string; value: string; description: string }[]; 29 | options: any; 30 | graphql: { query: string; variables: string }; 31 | format: boolean; 32 | } 33 | 34 | const initialState: RequestBodyState = { 35 | disabled: true, 36 | format: false, 37 | raw: "", 38 | file: "", 39 | fileData: "", 40 | formdata: [], 41 | urlencoded: [], 42 | options: { raw: { language: "json" } }, 43 | graphql: { query: "", variables: "{}" }, 44 | }; 45 | 46 | const requestBodySlice = createSlice({ 47 | name: "requestBody", 48 | initialState, 49 | reducers: { 50 | requestBodyFormDataItemAdded(state, action: PayloadAction) { 51 | state.formdata.push(action.payload); 52 | }, 53 | requestBodyFormDataItemDeleted(state, action: PayloadAction) { 54 | state.formdata.splice(action.payload, 1); 55 | }, 56 | requestBodyFormDataItemUpdated(state, action: PayloadAction) { 57 | state.formdata[action.payload.idx] = action.payload.value; 58 | }, 59 | requestBodyUrlEncodedItemAdded(state, action: PayloadAction) { 60 | state.urlencoded.push(action.payload); 61 | }, 62 | requestBodyUrlEncodedItemDeleted(state, action: PayloadAction) { 63 | state.urlencoded.splice(action.payload, 1); 64 | }, 65 | requestBodyUrlEncodedItemUpdated(state, action: PayloadAction) { 66 | state.urlencoded[action.payload.idx] = action.payload.value; 67 | }, 68 | requestBodyRawUpdated(state, action: PayloadAction) { 69 | state.raw = action.payload; 70 | }, 71 | requestBodyRawFormatUpdated(state, action: PayloadAction) { 72 | state.format = action.payload; 73 | }, 74 | requestBodyRawLanguageUpdated(state, action: PayloadAction) { 75 | state.options.raw.language = action.payload; 76 | }, 77 | requestBodyBinaryUpdated(state, action: PayloadAction) { 78 | state.file = action.payload.name; 79 | state.fileData = action.payload.data; 80 | }, 81 | requestBodyGraphqlQueryUpdated(state, action: PayloadAction) { 82 | state.graphql.query = action.payload; 83 | }, 84 | requestBodyGraphqlVariablesUpdated(state, action: PayloadAction) { 85 | state.graphql.variables = action.payload; 86 | }, 87 | requestBodyModeUpdated(state, action: PayloadAction) { 88 | if (action.payload === "none") { 89 | state.mode = undefined; 90 | state.disabled = true; 91 | } else { 92 | state.mode = action.payload; 93 | state.disabled = false; 94 | } 95 | }, 96 | }, 97 | }); 98 | 99 | export const { 100 | requestBodyBinaryUpdated, 101 | requestBodyFormDataItemAdded, 102 | requestBodyFormDataItemDeleted, 103 | requestBodyFormDataItemUpdated, 104 | requestBodyModeUpdated, 105 | requestBodyRawUpdated, 106 | requestBodyRawFormatUpdated, 107 | requestBodyRawLanguageUpdated, 108 | requestBodyUrlEncodedItemAdded, 109 | requestBodyUrlEncodedItemDeleted, 110 | requestBodyUrlEncodedItemUpdated, 111 | requestBodyGraphqlQueryUpdated, 112 | requestBodyGraphqlVariablesUpdated, 113 | } = requestBodySlice.actions; 114 | 115 | export const selectRequestBody = (state: RootState) => state.requestBody; 116 | export const selectRequestBodyMode = (state: RootState) => 117 | state.requestBody.mode || "none"; 118 | export const selectRequestBodyRaw = (state: RootState) => state.requestBody.raw; 119 | export const selectRequestBodyRawLanguage = (state: RootState) => 120 | state.requestBody.options.raw.language; 121 | export const selectRequestBodyFile = (state: RootState) => 122 | state.requestBody.file; 123 | export const selectRequestBodyFileData = (state: RootState) => 124 | state.requestBody.fileData; 125 | export const selectRequestBodyFormData = (state: RootState) => 126 | state.requestBody.formdata; 127 | export const selectRequestBodyUrlEncoded = (state: RootState) => 128 | state.requestBody.urlencoded; 129 | export const selectRequestBodyGraphqlQuery = (state: RootState) => 130 | state.requestBody.graphql.query; 131 | export const selectRequestBodyGraphqlVariables = (state: RootState) => 132 | state.requestBody.graphql.variables; 133 | export const selectRequestBodyRawFormat = (state: RootState) => 134 | state.requestBody.format; 135 | 136 | export default requestBodySlice.reducer; 137 | -------------------------------------------------------------------------------- /webview/features/requestHeader/HeadersWindow/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { KeyValueTable } from "../../../shared/KeyValueTable"; 4 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 5 | import { 6 | requestHeaderAdded, 7 | requestHeaderUpdated, 8 | requestHeaderDeleted, 9 | selectRequestHeaders, 10 | } from "../requestHeaderSlice"; 11 | 12 | export const Headers = () => { 13 | const header = useAppSelector(selectRequestHeaders); 14 | const dispatch = useAppDispatch(); 15 | 16 | return ( 17 |
18 | dispatch(requestHeaderAdded(value))} 21 | onRowDelete={(idx) => dispatch(requestHeaderDeleted(idx))} 22 | onRowUpdate={(idx, value) => 23 | dispatch(requestHeaderUpdated({ idx, value })) 24 | } 25 | /> 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /webview/features/requestHeader/HeadersWindow/styles.css: -------------------------------------------------------------------------------- 1 | .headers-wrapper { 2 | margin-top: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /webview/features/requestHeader/requestHeaderSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | 4 | interface Header { 5 | key: string; 6 | value: string; 7 | description: string; 8 | disabled: boolean; 9 | } 10 | 11 | const initialState: Header[] = [ 12 | { 13 | key: "Cache-Control", 14 | value: "no-cache", 15 | description: "", 16 | disabled: false, 17 | }, 18 | { 19 | key: "Accept", 20 | value: "*/*", 21 | description: "", 22 | disabled: false, 23 | }, 24 | { 25 | key: "Accept-Encoding", 26 | value: "gzip, deflate", 27 | description: "", 28 | disabled: false, 29 | }, 30 | { 31 | key: "Connection", 32 | value: "keep-alive", 33 | description: "", 34 | disabled: false, 35 | }, 36 | ]; 37 | 38 | const requestHeaderSlice = createSlice({ 39 | name: "requestHeader", 40 | initialState, 41 | reducers: { 42 | requestHeaderUpdated(state, action: PayloadAction) { 43 | state[action.payload.idx] = action.payload.value; 44 | }, 45 | requestHeaderAdded(state, action: PayloadAction
) { 46 | state.push(action.payload); 47 | }, 48 | requestHeaderDeleted(state, action: PayloadAction) { 49 | state.splice(action.payload, 1); 50 | }, 51 | }, 52 | }); 53 | 54 | export const { 55 | requestHeaderAdded, 56 | requestHeaderUpdated, 57 | requestHeaderDeleted, 58 | } = requestHeaderSlice.actions; 59 | 60 | export const selectRequestHeaders = (state: RootState) => state.requestHeader; 61 | 62 | export default requestHeaderSlice.reducer; 63 | -------------------------------------------------------------------------------- /webview/features/requestMethod/RequestMethodSelector/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 3 | import { 4 | requestMethodUpdated, 5 | selectRequestMethod, 6 | requestMethods, 7 | } from "../requestMethodSlice"; 8 | import "./styles.css"; 9 | 10 | export const RequestMethodSelector = () => { 11 | const requestMethod = useAppSelector(selectRequestMethod); 12 | const dispatch = useAppDispatch(); 13 | 14 | return ( 15 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /webview/features/requestMethod/RequestMethodSelector/styles.css: -------------------------------------------------------------------------------- 1 | .request-method-selector { 2 | padding: 12px 20px; 3 | display: inline-block; 4 | font-weight: 600; 5 | outline: none; 6 | border: 0; 7 | border-radius: 2px 0 0 2px; 8 | background-color: var(--input-field); 9 | color: var(--default-text); 10 | cursor: pointer; 11 | font-size: var(--default-font-size); 12 | } 13 | .request-method-selector:hover { 14 | background-color: var(--input-field-hover); 15 | } 16 | .request-method-selector:focus { 17 | outline: none; 18 | } 19 | -------------------------------------------------------------------------------- /webview/features/requestMethod/requestMethodSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | 4 | export const requestMethods = [ 5 | { name: "GET", value: "get" }, 6 | { name: "POST", value: "post" }, 7 | { name: "PUT", value: "put" }, 8 | { name: "PATCH", value: "patch" }, 9 | { name: "DELETE", value: "delete" }, 10 | { name: "OPTIONS", value: "options" }, 11 | { name: "HEAD", value: "head" }, 12 | ]; 13 | 14 | const initialState = "get"; 15 | 16 | const requestMethodSlice = createSlice({ 17 | name: "requestMethod", 18 | initialState, 19 | reducers: { 20 | requestMethodUpdated(state, action: PayloadAction) { 21 | return action.payload; 22 | }, 23 | }, 24 | }); 25 | 26 | export const { requestMethodUpdated } = requestMethodSlice.actions; 27 | 28 | export const selectRequestMethod = (state: RootState) => state.requestMethod; 29 | 30 | export default requestMethodSlice.reducer; 31 | -------------------------------------------------------------------------------- /webview/features/requestOptions/RequestOptions/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { useAppSelector } from "../../../redux/hooks"; 4 | import { 5 | requestOptionsUpdated, 6 | selectRequestOptions, 7 | requestOptionsList, 8 | } from "../requestOptionsSlice"; 9 | import "./styles.css"; 10 | 11 | export const RequestOptions = () => { 12 | const requestOptions = useAppSelector(selectRequestOptions); 13 | const dispatch = useDispatch(); 14 | 15 | return ( 16 |
17 |
18 | {requestOptionsList.map(({ name, value, type, ...optionDetails }) => ( 19 | 20 |
{`${name}: `}
21 | { 22 | type === "select" ? ( 23 | 41 | ) : null 42 | // Note: Augment this switch later with different renderers for 43 | // different types of options 44 | } 45 |
46 | ))} 47 |
48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /webview/features/requestOptions/RequestOptions/styles.css: -------------------------------------------------------------------------------- 1 | .req-options-wrapper { 2 | margin-top: 15px; 3 | } 4 | 5 | .options { 6 | display: flex; 7 | align-items: center; 8 | } 9 | 10 | .req-option-label { 11 | font-size: var(--default-font-size); 12 | color: var(--default-text); 13 | padding-right: 12px; 14 | } 15 | 16 | .req-option-switch { 17 | padding: 4px; 18 | display: inline-block; 19 | font-weight: 600; 20 | outline: none; 21 | border: var(--default-border-size) solid var(--border); 22 | border-radius: 2px; 23 | background-color: var(--background); 24 | color: var(--default-text-light); 25 | cursor: pointer; 26 | font-size: var(--default-font-size); 27 | } -------------------------------------------------------------------------------- /webview/features/requestOptions/requestOptionsSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | 4 | export const requestOptionsList = [ 5 | { 6 | name: "Strict SSL", 7 | value: "strictSSL", 8 | type: "select", 9 | options: [ 10 | { key: "yes", value: "Yes" }, 11 | { key: "no", value: "No" }, 12 | ], 13 | default: "Yes", 14 | }, 15 | ]; 16 | 17 | export interface RequestOptions { 18 | strictSSL: string; 19 | } 20 | 21 | const initialState = { 22 | strictSSL: "yes", 23 | }; 24 | 25 | const requestOptionsSlice = createSlice({ 26 | name: "requestOptions", 27 | initialState, 28 | reducers: { 29 | requestOptionsUpdated(_, action: PayloadAction) { 30 | return action.payload; 31 | }, 32 | }, 33 | }); 34 | 35 | export const { requestOptionsUpdated } = requestOptionsSlice.actions; 36 | 37 | export const selectRequestOptions = (state: RootState) => state.requestOptions; 38 | 39 | export default requestOptionsSlice.reducer; 40 | -------------------------------------------------------------------------------- /webview/features/requestUrl/RequestQueryParams/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { KeyValueTable } from "../../../shared/KeyValueTable"; 4 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 5 | import { 6 | requestQueryParamAdded, 7 | requestQueryParamDeleted, 8 | requestQueryParamUpdated, 9 | selectRequestQueryParams, 10 | } from "../requestUrlSlice"; 11 | 12 | export const RequestQueryParams = () => { 13 | const queryParams = useAppSelector(selectRequestQueryParams); 14 | const dispatch = useAppDispatch(); 15 | 16 | return ( 17 |
18 | dispatch(requestQueryParamAdded(value))} 21 | onRowDelete={(idx) => dispatch(requestQueryParamDeleted(idx))} 22 | onRowUpdate={(idx, value) => 23 | dispatch(requestQueryParamUpdated({ idx, value })) 24 | } 25 | /> 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /webview/features/requestUrl/RequestQueryParams/styles.css: -------------------------------------------------------------------------------- 1 | .params-wrapper { 2 | margin-top: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /webview/features/requestUrl/RequestUrl/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; 3 | import { requestUrlUpdated, selectRequestUrl } from "../requestUrlSlice"; 4 | import "./styles.css"; 5 | 6 | export const RequestUrl = () => { 7 | const requestUrl = useAppSelector(selectRequestUrl); 8 | const dispatch = useAppDispatch(); 9 | 10 | return ( 11 | dispatch(requestUrlUpdated(e.target.value))} 16 | /> 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /webview/features/requestUrl/RequestUrl/styles.css: -------------------------------------------------------------------------------- 1 | .input-request-url { 2 | flex: 2; 3 | padding: 13px 20px; 4 | color: var(--default-text); 5 | display: inline-block; 6 | outline: none; 7 | border: 0; 8 | border-radius: 0 2px 2px 0; 9 | background-color: var(--input-field); 10 | font-size: var(--default-font-size); 11 | } 12 | .input-request-url:focus { 13 | outline: none; 14 | } 15 | .input-request-url:hover { 16 | background-color: var(--input-field-hover); 17 | } 18 | -------------------------------------------------------------------------------- /webview/features/requestUrl/requestUrlSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | import { Url } from "postman-collection"; 4 | 5 | interface QueryParam { 6 | key: string; 7 | value: string; 8 | description: string; 9 | disabled: boolean; 10 | } 11 | 12 | interface Variable { 13 | key: string; 14 | value: string; 15 | description: string; 16 | } 17 | 18 | export interface RequestUrlState { 19 | auth?: string; 20 | hash?: string; 21 | host?: string[]; 22 | path?: string[]; 23 | port?: string; 24 | protocol?: string; 25 | query: QueryParam[]; 26 | variables: Variable[]; 27 | } 28 | 29 | const initialState: RequestUrlState = { 30 | query: [], 31 | variables: [], 32 | }; 33 | 34 | const requestUrlSlice = createSlice({ 35 | name: "requestUrl", 36 | initialState, 37 | reducers: { 38 | requestUrlUpdated(state, action: PayloadAction) { 39 | const { query, ...other } = Url.parse(action.payload); 40 | return { 41 | ...other, 42 | query: [ 43 | ...state.query.filter(({ disabled }) => disabled), 44 | ...(query || []), 45 | ], 46 | }; 47 | }, 48 | requestQueryParamAdded(state, action: PayloadAction) { 49 | state.query.push(action.payload); 50 | }, 51 | requestQueryParamUpdated(state, action: PayloadAction) { 52 | state.query[action.payload.idx] = action.payload.value; 53 | }, 54 | requestQueryParamDeleted(state, action: PayloadAction) { 55 | state.query.splice(action.payload, 1); 56 | }, 57 | // requestUrlVariableUpdated(state, action: PayloadAction) {}, 58 | }, 59 | }); 60 | 61 | export const { 62 | requestUrlUpdated, 63 | requestQueryParamAdded, 64 | requestQueryParamUpdated, 65 | requestQueryParamDeleted, 66 | // requestUrlVariableUpdated, 67 | } = requestUrlSlice.actions; 68 | 69 | export const selectRequestUrl = (state: RootState) => 70 | new Url(state.requestUrl).toString(); 71 | export const selectRequestQueryParams = (state: RootState) => 72 | state.requestUrl.query; 73 | export const selectRequestVariables = (state: RootState) => 74 | state.requestUrl.variables; 75 | 76 | export default requestUrlSlice.reducer; 77 | -------------------------------------------------------------------------------- /webview/features/response/ResponseBody/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import { useAppSelector } from "../../../redux/hooks"; 4 | import { selectResponse } from "../responseSlice"; 5 | import * as propTypes from "prop-types"; 6 | 7 | const Editor = React.lazy(() => import("../../../shared/Editor")); 8 | 9 | export const ResponseBody = (props) => { 10 | const { language } = props; 11 | const response = useAppSelector(selectResponse); 12 | 13 | return ( 14 |
15 | loading
}> 16 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | ResponseBody.propTypes = { 30 | language: propTypes.string.isRequired, 31 | }; 32 | -------------------------------------------------------------------------------- /webview/features/response/ResponseBody/styles.css: -------------------------------------------------------------------------------- 1 | .response-window { 2 | height: 100%; 3 | } 4 | 5 | .response-editor { 6 | height: 95%; 7 | } 8 | -------------------------------------------------------------------------------- /webview/features/response/ResponseHeaders/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useAppSelector } from "../../../redux/hooks"; 3 | import { KeyValueTable } from "../../../shared/KeyValueTable"; 4 | import { selectResponseHeaders } from "../responseSlice"; 5 | import "./styles.css"; 6 | 7 | export const ResponseHeaders = () => { 8 | const headers = useAppSelector(selectResponseHeaders); 9 | 10 | return ( 11 |
12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /webview/features/response/ResponseHeaders/styles.css: -------------------------------------------------------------------------------- 1 | .response-headers { 2 | display: flex; 3 | height: 80%; 4 | } -------------------------------------------------------------------------------- /webview/features/response/ResponseTab/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./styles.css"; 3 | import * as propTypes from "prop-types"; 4 | import { responseOptions } from "../../../constants/response-options"; 5 | import { supportedLangs } from "../../../constants/supported-langs"; 6 | import { useAppSelector } from "../../../redux/hooks"; 7 | import { selectResponse } from "../responseSlice"; 8 | 9 | const ResponseInfo = ({ responseTitle, info }) => { 10 | return ( 11 | <> 12 |
{responseTitle}
13 |
{info}
14 | 15 | ); 16 | }; 17 | 18 | export const ResponseTab = (props) => { 19 | const { selected, setSelected, language, setLanguage } = props; 20 | const response = useAppSelector(selectResponse); 21 | 22 | return ( 23 |
24 |
25 | {responseOptions.map((option) => ( 26 | 37 | ))} 38 | {selected === "body" ? ( 39 | 50 | ) : null} 51 |
52 |
53 | 57 | 61 |
62 |
63 | ); 64 | }; 65 | 66 | ResponseInfo.propTypes = { 67 | responseTitle: propTypes.string.isRequired, 68 | info: propTypes.string.isRequired, 69 | }; 70 | 71 | ResponseTab.propTypes = { 72 | selected: propTypes.string.isRequired, 73 | setSelected: propTypes.func.isRequired, 74 | language: propTypes.string.isRequired, 75 | setLanguage: propTypes.func.isRequired, 76 | }; 77 | -------------------------------------------------------------------------------- /webview/features/response/ResponseTab/styles.css: -------------------------------------------------------------------------------- 1 | .response-options-tab-wrapper { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | padding: 15px 20px 5px 20px; 6 | } 7 | 8 | .response-options { 9 | display: flex; 10 | } 11 | 12 | .response-option { 13 | padding: 5px 5px 5px 0; 14 | margin: 0 10px 0 0; 15 | display: flex; 16 | cursor: pointer; 17 | background-color: transparent; 18 | color: var(--default-text-light); 19 | outline: none; 20 | border: none; 21 | border-radius: 0; 22 | font-weight: 500; 23 | font-size: var(--default-font-size); 24 | } 25 | .response-option:hover { 26 | color: var(--default-text); 27 | } 28 | 29 | .response-option-selected { 30 | color: var(--default-text); 31 | } 32 | 33 | .response-options-header-length { 34 | margin-left: 4px; 35 | color: var(--tab-info); 36 | } 37 | 38 | .response-status { 39 | font-size: var(--default-font-size); 40 | display: flex; 41 | align-items: center; 42 | } 43 | 44 | .text-response-info { 45 | color: var(--tab-info); 46 | margin-left: 5px; 47 | margin-right: 5px; 48 | } 49 | 50 | .select-res-lang { 51 | margin-left: 10px; 52 | padding: 5px; 53 | display: inline-block; 54 | font-weight: 600; 55 | outline: none; 56 | border: var(--default-border-size) solid var(--border); 57 | border-radius: 2px; 58 | background-color: var(--background); 59 | color: var(--default-text-light); 60 | cursor: pointer; 61 | font-size: var(--small-font-size); 62 | } 63 | .select-res-lang:hover { 64 | color: var(--default-text); 65 | } 66 | .select-res-lang:focus { 67 | outline: none; 68 | } -------------------------------------------------------------------------------- /webview/features/response/ResponseWindow/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as propTypes from "prop-types"; 3 | import "./styles.css"; 4 | import { ResponseBody } from "../ResponseBody"; 5 | import { ResponseHeaders } from "../ResponseHeaders"; 6 | 7 | export const ResponseWindow = (props) => { 8 | const { selected, language } = props; 9 | return ( 10 |
11 | {selected === "body" ? ( 12 | 13 | ) : selected === "headers" ? ( 14 | 15 | ) : null} 16 |
17 | ); 18 | }; 19 | 20 | ResponseWindow.propTypes = { 21 | selected: propTypes.string.isRequired, 22 | language: propTypes.string.isRequired, 23 | }; 24 | -------------------------------------------------------------------------------- /webview/features/response/ResponseWindow/styles.css: -------------------------------------------------------------------------------- 1 | .response-options-window-wrapper { 2 | flex: 1; 3 | padding: 5px 20px 10px 20px; 4 | } 5 | -------------------------------------------------------------------------------- /webview/features/response/responseSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "../../redux/store"; 3 | 4 | export interface ResponseState { 5 | status?: number; 6 | statusText?: string; 7 | data?: string; 8 | initial: boolean; 9 | error?: Error; 10 | loading?: boolean; 11 | headers?: { key: string; value: string }[]; 12 | duration?: number; 13 | } 14 | 15 | const initialState: ResponseState = { initial: true }; 16 | 17 | const responseSlice = createSlice({ 18 | name: "response", 19 | initialState, 20 | reducers: { 21 | responseUpdated(state, action: PayloadAction) { 22 | return { 23 | ...action.payload, 24 | headers: 25 | action.payload.headers && 26 | Object.entries(action.payload.headers).map(([key, value]) => ({ 27 | key, 28 | value, 29 | })), 30 | initial: false, 31 | loading: false, 32 | }; 33 | }, 34 | responseLoadingStarted(state) { 35 | state.loading = true; 36 | }, 37 | }, 38 | }); 39 | 40 | export const { responseUpdated, responseLoadingStarted } = 41 | responseSlice.actions; 42 | 43 | export const selectResponse = (state: RootState) => state.response; 44 | export const selectResponseHeaders = (state: RootState) => 45 | state.response.headers; 46 | 47 | export default responseSlice.reducer; 48 | -------------------------------------------------------------------------------- /webview/icons/package.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /webview/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | --primary: var(--vscode-button-hoverBackground); 9 | --primary-dark: var(--vscode-button-background); 10 | --background: var(--vscode-editor-background); 11 | --input-field: var(--vscode-input-background); 12 | --input-field-focus: var(--vscode-input-background); 13 | --input-field-hover: var(--vscode-input-background); 14 | --input-field-unchecked: #808080; 15 | --default-text: var(--vscode-foreground); 16 | --default-text-light: var(--vscode-descriptionForeground); 17 | --border: var(--vscode-tree-tableColumnsBorder); 18 | --send-button: var(--vscode-button-background); 19 | --send-button-hover: var(--vscode-button-hoverBackground); 20 | --tab-info: #259c47; 21 | --logo-color: var(--vscode-tree-tableColumnsBorder); 22 | --error-message: var(--vscode-errorForeground); 23 | --default-font-size: var(--vscode-editor-font-size); 24 | --small-font-size: width: calc(var(--vscode-editor-font-size) * 0.727);; 25 | --default-border-size: 0.1px; 26 | } 27 | 28 | html, 29 | body, 30 | #root, 31 | .App { 32 | height: 100%; 33 | } 34 | 35 | code { 36 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 37 | monospace; 38 | } 39 | -------------------------------------------------------------------------------- /webview/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { store } from "./redux/store"; 5 | import "./index.css"; 6 | import App from "./App"; 7 | 8 | ReactDOM.hydrate( 9 | 10 | 11 | , 12 | document.getElementById("root") 13 | ); 14 | -------------------------------------------------------------------------------- /webview/pages/Postcode/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import * as React from "react"; 3 | import "./styles.css"; 4 | import { RequestBar } from "../../components/RequestBar"; 5 | import { RequestOptionsTab } from "../../components/RequestOptionsBar"; 6 | import { RequestOptionsWindow } from "../../components/RequestOptionsWindow"; 7 | import { Response } from "../../components/Response"; 8 | import { requestOptions } from "../../constants/request-options"; 9 | 10 | export const Postcode = () => { 11 | const [selectedOption, setSelectedOption] = React.useState( 12 | requestOptions[0].value 13 | ); 14 | 15 | return ( 16 |
17 | 18 |
19 | 23 | 24 |
25 |
26 | 27 |
28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /webview/pages/Postcode/styles.css: -------------------------------------------------------------------------------- 1 | .request-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | height: inherit; 5 | } 6 | 7 | .request-options-wrapper { 8 | display: flex; 9 | flex-direction: column; 10 | min-height: 50%; 11 | } 12 | 13 | .response-wrapper { 14 | display: flex; 15 | flex-direction: column; 16 | height: 50%; 17 | } 18 | -------------------------------------------------------------------------------- /webview/prerender.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from "react-redux"; 2 | import * as React from "react"; 3 | import { renderToString } from "react-dom/server"; 4 | import App from "./App"; 5 | import { store } from "./redux/store"; 6 | 7 | export default () => { 8 | const html = renderToString( 9 | 10 | 11 | 12 | ); 13 | 14 | const preloadedState = store.getState(); 15 | 16 | return ` 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
${html}
27 | 33 | 34 | 35 | 36 | `; 37 | }; 38 | -------------------------------------------------------------------------------- /webview/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: "development" | "production" | "test"; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module "*.avif" { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module "*.bmp" { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module "*.gif" { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module "*.jpg" { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module "*.jpeg" { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module "*.png" { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module "*.webp" { 43 | const src: string; 44 | export default src; 45 | } 46 | 47 | declare module "*.svg" { 48 | import * as React from "react"; 49 | 50 | export const ReactComponent: React.FunctionComponent< 51 | React.SVGProps & { title?: string } 52 | >; 53 | 54 | const src: string; 55 | export default src; 56 | } 57 | 58 | declare module "*.module.css" { 59 | const classes: { readonly [key: string]: string }; 60 | export default classes; 61 | } 62 | 63 | declare module "*.module.scss" { 64 | const classes: { readonly [key: string]: string }; 65 | export default classes; 66 | } 67 | 68 | declare module "*.module.sass" { 69 | const classes: { readonly [key: string]: string }; 70 | export default classes; 71 | } 72 | -------------------------------------------------------------------------------- /webview/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | import type { RootState, AppDispatch } from "./store"; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch = () => useDispatch(); 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /webview/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"; 2 | import requestAuthReducer from "../features/requestAuth/requestAuthSlice"; 3 | import requestBodyReducer from "../features/requestBody/requestBodySlice"; 4 | import requestHeaderReducer from "../features/requestHeader/requestHeaderSlice"; 5 | import requestMethodReducer from "../features/requestMethod/requestMethodSlice"; 6 | import requestUrlReducer from "../features/requestUrl/requestUrlSlice"; 7 | import responseReducer from "../features/response/responseSlice"; 8 | import codeGenOptionsReducer from "../features/codeGen/codeGenSlice"; 9 | import requestOptionsReducer from "../features/requestOptions/requestOptionsSlice"; 10 | 11 | let preloadedState; 12 | if (typeof window !== "undefined") { 13 | preloadedState = (window as any).__PRELOADED_STATE__; 14 | delete (window as any).__PRELOADED_STATE__; 15 | } 16 | 17 | export const store = configureStore({ 18 | reducer: { 19 | requestAuth: requestAuthReducer, 20 | requestBody: requestBodyReducer, 21 | requestHeader: requestHeaderReducer, 22 | requestMethod: requestMethodReducer, 23 | requestUrl: requestUrlReducer, 24 | response: responseReducer, 25 | codeGenOptions: codeGenOptionsReducer, 26 | requestOptions: requestOptionsReducer, 27 | }, 28 | preloadedState, 29 | }); 30 | 31 | export type AppDispatch = typeof store.dispatch; 32 | export type RootState = ReturnType; 33 | export type AppThunk = ThunkAction< 34 | ReturnType, 35 | RootState, 36 | unknown, 37 | Action 38 | >; 39 | -------------------------------------------------------------------------------- /webview/shared/Editor/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as monaco from "monaco-editor"; 3 | import * as propTypes from "prop-types"; 4 | import "./styles.css"; 5 | import { useAppDispatch } from "../../redux/hooks"; 6 | import { requestBodyRawFormatUpdated } from "../../features/requestBody/requestBodySlice"; 7 | 8 | const Editor = (props) => { 9 | const { value, language, onChange, readOnly, className, copyButton, format } = 10 | props; 11 | 12 | const divEl = React.useRef(null); 13 | const [editor, setEditor] = React.useState(undefined); 14 | const [copy, setCopy] = React.useState("Copy"); 15 | const dispatch = useAppDispatch(); 16 | 17 | React.useEffect(() => { 18 | if (divEl.current) { 19 | const tmpEditor = monaco.editor.create(divEl.current, { 20 | minimap: { enabled: false }, 21 | scrollBeyondLastLine: false, 22 | theme: "vs-dark", 23 | value, 24 | language, 25 | readOnly, 26 | }); 27 | 28 | window.addEventListener("resize", () => { 29 | tmpEditor.layout(); 30 | }); 31 | 32 | if (onChange) { 33 | tmpEditor.onDidChangeModelContent(() => { 34 | onChange(tmpEditor.getValue()); 35 | }); 36 | } 37 | 38 | setEditor(tmpEditor); 39 | 40 | return () => { 41 | tmpEditor.dispose(); 42 | }; 43 | } 44 | }, []); 45 | 46 | React.useEffect(() => { 47 | if (editor) { 48 | if (editor.getValue() !== value) { 49 | editor.setValue(value); 50 | } 51 | 52 | const model = editor.getModel(); 53 | monaco.editor.setModelLanguage(model, language); 54 | if (format) { 55 | editor.updateOptions({ readOnly: false }); 56 | setTimeout(() => { 57 | editor 58 | .getAction("editor.action.formatDocument") 59 | .run() 60 | .then(() => { 61 | editor.updateOptions({ readOnly }); 62 | dispatch(requestBodyRawFormatUpdated(false)); 63 | }); 64 | }, 300); 65 | } 66 | } 67 | }, [value, language, editor, format]); 68 | 69 | return ( 70 |
71 | {copyButton && ( 72 | 83 | )} 84 |
85 | ); 86 | }; 87 | 88 | Editor.propTypes = { 89 | value: propTypes.string.isRequired, 90 | language: propTypes.string.isRequired, 91 | onChange: propTypes.func, 92 | className: propTypes.string, 93 | readOnly: propTypes.bool, 94 | copyButton: propTypes.bool, 95 | format: propTypes.bool, 96 | }; 97 | 98 | export default Editor; 99 | -------------------------------------------------------------------------------- /webview/shared/Editor/styles.css: -------------------------------------------------------------------------------- 1 | .monaco-editor { 2 | border: var(--default-border-size) solid var(--border); 3 | } 4 | 5 | .postcode-editor { 6 | position: relative; 7 | } 8 | 9 | .copy-button { 10 | font-size: var(--default-font-size); 11 | background-color: rgb(51, 51, 51); 12 | border: none; 13 | color: #fff; 14 | border-radius: 0.4rem; 15 | cursor: pointer; 16 | outline: none; 17 | padding: 0.4rem 0.5rem; 18 | position: absolute; 19 | transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; 20 | z-index: 10; 21 | right: 0.5rem; 22 | top: 0.5rem; 23 | opacity: 0; 24 | visibility: hidden; 25 | } 26 | 27 | .postcode-editor:hover .copy-button { 28 | opacity: 1; 29 | visibility: visible; 30 | } 31 | -------------------------------------------------------------------------------- /webview/shared/KeyValueTable/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as propTypes from "prop-types"; 3 | import { FaTrashAlt } from "react-icons/fa"; 4 | import "./styles.css"; 5 | 6 | export const KeyValueRow = (props) => { 7 | const { 8 | itemKey, 9 | itemValue, 10 | itemDescription, 11 | itemDisabled, 12 | actions, 13 | onDelete, 14 | onChange, 15 | fixed, 16 | } = props; 17 | 18 | return ( 19 | 20 | {!fixed && ( 21 | 22 | {actions && ( 23 | 27 | onChange({ 28 | key: itemKey, 29 | value: itemValue, 30 | description: itemDescription, 31 | disabled: !e.target.checked, 32 | }) 33 | } 34 | /> 35 | )} 36 | 37 | )} 38 | 39 | 45 | onChange({ 46 | key: e.target.value, 47 | value: itemValue, 48 | description: itemDescription, 49 | disabled: itemDisabled, 50 | }) 51 | } 52 | /> 53 | 54 | 55 | 61 | onChange({ 62 | key: itemKey, 63 | value: e.target.value, 64 | description: itemDescription, 65 | disabled: itemDisabled, 66 | }) 67 | } 68 | /> 69 | 70 | {!fixed && ( 71 | 72 | 78 | onChange({ 79 | key: itemKey, 80 | value: itemValue, 81 | description: e.target.value, 82 | disabled: itemDisabled, 83 | }) 84 | } 85 | /> 86 | 87 | )} 88 | {!fixed && ( 89 | 90 | {actions && ( 91 | 92 | )} 93 | 94 | )} 95 | 96 | ); 97 | }; 98 | 99 | export const KeyValueTable = (props) => { 100 | const { data, fixed, onRowUpdate, onRowAdd, onRowDelete } = props; 101 | 102 | return ( 103 | 104 | 105 | 106 | {!fixed && } 107 | 108 | 109 | {!fixed && } 110 | {!fixed && } 111 | 112 | 113 | 114 | {(fixed ? data : [...data, {}]).map( 115 | ({ key, value, description, disabled }, idx) => ( 116 | onRowDelete(idx)} 123 | onChange={(item) => 124 | idx === data.length ? onRowAdd(item) : onRowUpdate(idx, item) 125 | } 126 | key={idx} 127 | actions={idx !== data.length} 128 | /> 129 | ) 130 | )} 131 | 132 |
KEYVALUEDESCRIPTION
133 | ); 134 | }; 135 | 136 | KeyValueTable.propTypes = { 137 | data: propTypes.array.isRequired, 138 | fixed: propTypes.bool, 139 | onRowDelete: propTypes.func, 140 | onRowAdd: propTypes.func, 141 | onRowUpdate: propTypes.func, 142 | }; 143 | 144 | KeyValueRow.propTypes = { 145 | fixed: propTypes.bool, 146 | itemKey: propTypes.string.isRequired, 147 | itemValue: propTypes.string.isRequired, 148 | itemDescription: propTypes.string.isRequired, 149 | itemDisabled: propTypes.bool.isRequired, 150 | actions: propTypes.bool.isRequired, 151 | onChange: propTypes.func.isRequired, 152 | onDelete: propTypes.func.isRequired, 153 | }; 154 | -------------------------------------------------------------------------------- /webview/shared/KeyValueTable/styles.css: -------------------------------------------------------------------------------- 1 | .kv-table { 2 | border-collapse: collapse; 3 | width: 100%; 4 | } 5 | 6 | .kv-table, 7 | td, 8 | th { 9 | border: var(--default-border-size) solid var(--border); 10 | } 11 | 12 | th, 13 | .kv-input { 14 | color: var(--default-text); 15 | padding: 10px 12px; 16 | } 17 | 18 | th { 19 | text-align: left; 20 | font-weight: 700; 21 | font-size: var(--small-font-size); 22 | } 23 | 24 | .kv-action-cell { 25 | text-align: center; 26 | width: 30px; 27 | } 28 | 29 | .kv-input { 30 | box-sizing: border-box; 31 | width: 100%; 32 | border: 0; 33 | background-color: transparent; 34 | font-size: var(--default-font-size); 35 | } 36 | 37 | .kv-input:focus { 38 | outline: none; 39 | } 40 | 41 | .kv-disabled .kv-input { 42 | color: var(--input-field-unchecked); 43 | } 44 | 45 | .kv-delete-button { 46 | cursor: pointer; 47 | } 48 | -------------------------------------------------------------------------------- /webview/vscode.ts: -------------------------------------------------------------------------------- 1 | let vscode; 2 | if (typeof acquireVsCodeApi !== "undefined") { 3 | vscode = acquireVsCodeApi(); 4 | } 5 | 6 | export default vscode; 7 | --------------------------------------------------------------------------------