diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..24dff8942 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +/dist +/build +/node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 012ba867b..7f8849c9d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,77 +1,189 @@ -module.exports = { - env: { - es6: true, - jest: true, - node: true, - }, - extends: [ - "airbnb", - "prettier", - "prettier/@typescript-eslint", - "prettier/react", - ], - globals: { - Atomics: "readonly", - SharedArrayBuffer: "readonly", - }, - parser: "babel-eslint", - - parserOptions: { - ecmaVersion: 2020, - ecmaFeatures: { - jsx: true, - classes: true, - }, - }, - plugins: ["react", "jsx-a11y", "import", "jest", "react-hooks"], - rules: { - "arrow-body-style": 0, - "class-methods-use-this": 0, - "consistent-return": 0, - "comma-dangle": 0, - "dot-notation": 0, - "func-names": 0, - "guard-for-in": 0, - "import/extensions": 0, - "import/no-extraneous-dependencies": 0, - "import/no-unresolved": 0, - "import/prefer-default-export": 0, - "max-len": 0, - "no-alert": 0, - "no-console": 0, - "no-param-reassign": 0, - "no-plusplus": 0, - "no-restricted-globals": 1, - "no-restricted-syntax": 0, - "no-shadow": 0, - "no-undef": 0, - "no-unused-vars": 0, - "no-use-before-define": 0, - "no-useless-constructor": 0, - "no-underscore-dangle": 0, - "no-unused-expressions": 0, - "no-return-assign": 0, - "prefer-const": 1, - "prefer-destructuring": 0, - "prefer-template": 0, - "react/button-has-type": 0, - "react/destructuring-assignment": 0, - "react/forbid-prop-types": 0, - "react/jsx-filename-extension": 0, - "react/jsx-no-duplicate-props": 0, - "react/no-access-state-in-setstate": 0, - "react/no-array-index-key": 0, - "react/no-did-update-set-state": 0, - "react/no-unused-state": 0, - "react/prefer-stateless-function": 0, - "react/sort-comp": [ - 2, - { - order: ["lifecycle", "everything-else", "rendering"], - }, - ], - "react/prop-types": 0, - "spaced-comment": 0, - strict: 0, - }, -}; +module.exports = { + // Global ESLint Settings + // ================================= + root: true, + env: { + browser: true, + es6: true, + node: true, + jest: true, + }, + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + ignorePatterns: ['cypress/*'], + settings: { + react: { + version: 'detect', + }, + 'import/resolver': { + typescript: {}, + 'babel-module': { + root: ['.'], + alias: { + '~/static': './public/static/', + '~': './', + }, + }, + }, + }, + + // =========================================== + // Set up ESLint for .js / .jsx files + // =========================================== + // .js / .jsx uses babel-eslint + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + + // Plugins + // ================================= + plugins: ['react', 'jsx-a11y', 'import', 'jest', 'react-hooks', 'prettier'], + + // Extend Other Configs + // ================================= + extends: [ + 'airbnb', + 'eslint:recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:react/recommended', + // Disable rules that conflict with Prettier + // Prettier must be last to override other configs + 'prettier', + ], + rules: { + 'react/function-component-definition': 0, + 'react/boolean-prop-naming': 0, + 'react/prop-types': 0, + 'react-hooks/exhaustive-deps': 1, + 'react/react-in-jsx-scope': 0, + 'react/display-name': [0], + // from old + 'arrow-body-style': 0, + 'class-methods-use-this': 0, + 'consistent-return': 0, + 'comma-dangle': 0, + 'dot-notation': 0, + 'func-names': 0, + 'guard-for-in': 0, + 'import/extensions': 0, + 'import/no-extraneous-dependencies': 0, + 'import/no-unresolved': 0, + 'import/prefer-default-export': 0, + 'max-len': 0, + 'no-alert': 0, + 'no-console': 0, + 'no-param-reassign': 0, + 'no-plusplus': 0, + 'no-restricted-globals': 1, + 'no-restricted-syntax': 0, + 'no-shadow': 0, + 'no-undef': 0, + 'no-unused-vars': 0, + 'no-use-before-define': 0, + 'no-useless-constructor': 0, + 'no-underscore-dangle': 0, + 'no-unused-expressions': 0, + 'no-return-assign': 0, + 'prefer-const': 1, + 'prefer-destructuring': 0, + 'prefer-template': 0, + 'react/button-has-type': 0, + 'react/destructuring-assignment': 0, + 'react/forbid-prop-types': 0, + 'react/jsx-filename-extension': 0, + 'react/jsx-no-duplicate-props': 0, + 'react/no-access-state-in-setstate': 0, + 'react/no-array-index-key': 0, + 'react/no-did-update-set-state': 0, + 'react/no-unused-state': 0, + 'react/prefer-stateless-function': 0, + 'react/sort-comp': [ + 2, + { + order: ['lifecycle', 'everything-else', 'rendering'], + }, + ], + 'spaced-comment': 0, + strict: 0, + }, + + // ================================= + // Overrides for Specific Files + // ================================= + overrides: [ + // Match TypeScript Files + // ================================= + { + files: ['**/*.{ts,tsx}'], + + // Global ESLint Settings + // ================================= + env: { + jest: true, + }, + globals: { + React: 'writable', + }, + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + project: './tsconfig.json', + }, + }, + }, + + // Parser Settings + // ================================= + // allow ESLint to understand TypeScript syntax + // https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L10 + parser: '@typescript-eslint/parser', + parserOptions: { + // Lint with Type Information + // https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md + tsconfigRootDir: __dirname, + project: './tsconfig.json', + }, + + // Plugins + // ================================= + plugins: ['jsx-a11y'], + + // Extend Other Configs + // ================================= + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:react/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + 'prettier', + ], + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': [0], + // temp allowing during TS migration + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description', + minimumDescriptionLength: 4, + }, + ], + }, + }, + ], +}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2fcf1c6d0..fc1c2355d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,32 +1,33 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..34a125c1a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,19 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 3cc1c04d6..d6cf97dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,18 @@ # Build folder and files # ########################## -builds/ +build/ release/ # Development folders and files # ################################# .tmp/ dist/ -node_modules/ +node_modules *.compiled.* package-lock.json coverage/ .vscode +src/client/docs/ # Folder config file # ###################### diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..49807ceaf --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +/build +/dist +/node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..942702aad --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "singleQuote": true, + "arrowParens": "always" +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 970d305a2..d696fb36b 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,49 +1,58 @@ -# Contributing to Swell -We love your input! We want to make contributing to Swell as easy and transparent as possible, whether it's: - -- Reporting a bug -- Discussing the current state of the code -- Submitting a fix -- Proposing new features - -## We Develop with Github -We use Github to host code, to track issues and feature requests, as well as accept pull requests. - -## All Code Changes Happen Through Pull Requests -Pull requests are the best way to propose changes to Swell. We actively welcome your pull requests: - -1. Fork the repo and create your branch from `dev`. -2. If you've added code that should be tested, add tests. -3. If you've changed APIs, update the documentation. -4. Ensure the test suite passes. -5. Make sure your code lints. -6. Issue that pull request! - -## Any contributions you make will be under the MIT Software License -In short, when you submit code changes, your submissions are understood to be under the same that covers the project. Feel free to contact the maintainers if that's a concern. - -## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) -We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/open-source-labs/Swell/issues); it's that easy! - -## Write bug reports with detail, background, and sample code -**Great Bug Reports** tend to have: - -- A quick summary and/or background -- Steps to reproduce - - Be specific! - - Give sample code if you can. Include sample code that *anyone* can run to reproduce what you are experiencing -- What you expected would happen -- What actually happens -- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) - -People *love* thorough bug reports. I'm not even kidding. - -## Use a Consistent Coding Style -* 2 spaces for indentation rather than tabs -* You can try running `npm run lint` for style unification - -## License -By contributing, you agree that your contributions will be licensed under its MIT License. - -## References -This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) +# Contributing to Swell + +We love your input! We want to make contributing to Swell as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +## We Develop with Github + +We use Github to host code, to track issues and feature requests, as well as accept pull requests. + +## All Code Changes Happen Through Pull Requests + +Pull requests are the best way to propose changes to Swell. We actively welcome your pull requests: + +1. Fork the repo and create your branch from `dev`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Issue that pull request! + +## Any contributions you make will be under the MIT Software License + +In short, when you submit code changes, your submissions are understood to be under the same that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) + +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/open-source-labs/Swell/issues); it's that easy! + +## Write bug reports with detail, background, and sample code + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. Include sample code that _anyone_ can run to reproduce what you are experiencing +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +People _love_ thorough bug reports. I'm not even kidding. + +## Use a Consistent Coding Style + +- 2 spaces for indentation rather than tabs +- You can try running `npm run lint` for style unification + +## License + +By contributing, you agree that your contributions will be licensed under its MIT License. + +## References + +This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 409329a0b..1fa8faaa4 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,34 +1,34 @@ -# Issue Template - -## Prerequisites - -Please answer the following questions for yourself before submitting an issue. - -- [ ] I am running the latest version -- [ ] I checked the documentation and found no answer -- [ ] I checked to make sure that this issue has not already been filed -- [ ] I'm reporting the issue to the correct repository (for multi-repository projects) - -## Expected Behavior - -Please describe the behavior you are expecting - -## Current Behavior - -What is the current behavior? - -## Failure Information (for bugs) - -Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. - -### Steps to Reproduce - -Please provide detailed steps for reproducing the issue. - -1. step 1 -2. step 2 -3. and so on... - -### Failure Logs - -Please include any relevant log snippets or files here. \ No newline at end of file +# Issue Template + +## Prerequisites + +Please answer the following questions for yourself before submitting an issue. + +- [ ] I am running the latest version +- [ ] I checked the documentation and found no answer +- [ ] I checked to make sure that this issue has not already been filed +- [ ] I'm reporting the issue to the correct repository (for multi-repository projects) + +## Expected Behavior + +Please describe the behavior you are expecting + +## Current Behavior + +What is the current behavior? + +## Failure Information (for bugs) + +Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. + +### Steps to Reproduce + +Please provide detailed steps for reproducing the issue. + +1. step 1 +2. step 2 +3. and so on... + +### Failure Logs + +Please include any relevant log snippets or files here. diff --git a/README.md b/README.md index 155fab81f..b3f1d2a2d 100755 --- a/README.md +++ b/README.md @@ -1,131 +1,154 @@ -

- -# - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/open-source-labs/Swell/blob/master/LICENSE.txt) -[![Build Status](https://travis-ci.org/open-source-labs/Swell.svg?branch=master)](https://travis-ci.org/open-source-labs/Swell) -![GitHub package.json version](https://img.shields.io/github/package-json/v/open-source-labs/Swell?color=blue) -[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/getswell/getswell/issues) -[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Swell-%20For%20all%20your%20streaming%20API%20testing%20needs&url=https://www.getswell.io&hashtags=SSE,WebSocket,HTTP,API,developers) - -Swell is a API development tool that enables developers to test endpoints served over streaming technologies including Server-Sent Events (SSE), WebSockets, HTTP2, GraphQL and gRPC. - -## Getting Started - -Visit www.getswell.io to download the latest release. - -Swell is currently available for OS X, Linux and Windows. - -## Highlights - -Swell is a one-stop shop for sending and monitoring your API requests - -- Send and monitor streams over HTTP2 / HTTP1 (including SSEs) and WebSockets -- Create GraphQL queries, introspections, mutations, and subscriptions -- Provides full streaming testing support for gRPC -- View response timing information and history in an interactive chart for each request -- Save workspaces of multiple requests for later access -- Import and export workspaces for sharing -- Compose test suites in JavaScript with Chai-style TDD/BDD assertion syntax -- Execute a collection of requests in succession and receive clear visual feedback of each test's status -- Schedule requests on to be sent on a regular time interval to support endpoint functional validation tests - -## Supported Technologies - -- _HTTP2_: Swell supports full HTTP2 multiplexing of requests and responses. HTTP requests to the same host will be sent over the same connection. Swell will attempt to initiate an HTTP2 connection for all HTTPS requests by default, but will revert to HTTP1.1 for legacy servers. Multiple concurrent streams are allowed for each connection. - - -- _Server-Sent Events (SSE)_: Initiated by a simple toggle box, Swell displays SSE events one by one as they come in. Similar to HTTP2 streams, multiple open connection streams are allowed for SSE. - - -- _WebSocket (WS)_: Swell enables connecting directly to WebSocket servers with an HTTP handshake. Developers can directly send messages to the connected WS server. Messages are displayed in chatbox format, clearly indicating outgoing and incoming messages. - - -- _GraphQL_: Swell includes full support for all three root types of GraphQL - queries, mutations, and subscriptions as well as Introspection - with and without variables. Smart code editor allows for easy query creation. - - -- _gRPC_: Swell includes full support for all four streaming types of gRPC - unary, client stream, server stream, bidirectional stream. - - -## Additional Features - -- _Scripting in Swell_: Swell allows you to write assertion tests to aid in the test-driven development cycle of backend API services. - -- _Workspaces_: Swell allows you to save workspaces for easier testing of multiple requests. -- _Import/Export Workspaces_: Swell allows you to import and export workspaces, making it easy to share collections with your team. -- _Preview_: You can now view a rendered preview of certain API responses (HTML) - -- _Collection Runner_: You can also stage requests in the workspace and automate the process of sending off each one. No need to manually press send on each one, instead each request will fire off in the order of staging. - -- _Schedule Tests_: You can also automate sending requests to occur on a periodic basis. - - -## Built With - -- Electron -- React -- Redux -- Apollo Client -- Websockets -- gRPC-js -- VM2 -- Chart.js -- Bulma -- IndexedDB -- Chai -- Mocha - -## Authors - -- **Grace Kim** - [gracekiim](https://github.com/gracekiim) -- **Alex Sanhueza** - [alexsanhueza](https://github.com/alexsanhueza) -- **Wyatt Bell** - [wcbell51](https://github.com/wcbell51) -- **John Madrigal** - [johnmadrigal](https://github.com/johnmadrigal) -- **Michael Miller** - [mjmiguel](https://github.com/mjmiguel) -- **Hideaki Aomori** - [h15200](https://github.com/h15200) -- **Matt Gin** - [chinsonhoag](https://github.com/chunsonhoag) -- **Nick Healy** - [nickhealy](http://github.com/nickhealy) -- **Grace Spletzer** - [gspletzer](https://github.com/gspletzer) -- **Stephanie Wood** - [stephwood](https://github.com/stephwood) -- **Anthony Terruso** - [discrete projects](https://github.com/discrete-projects) -- **Brandon Marrero** - [brandon6190](https://github.com/brandon6190) -- **Jason Ou** - [jasonou1994](https://github.com/jasonou1994) -- **Kyle Combs** - [texpatnyc](https://github.com/texpatnyc) -- **Kwadwo Asamoah** - [addoasa](https://github.com/addoasa) -- **Abby Chao** - [abbychao](https://github.com/abbychao) -- **Amanda Flink** - [aflinky](https://github.com/aflinky) -- **Kajol Thapa** - [kajolthapa](https://github.com/kajolthapa) -- **Billy Tran** - [btctrl](https://github.com/btctrl) -- **Paul Rhee** - [prheee](https://github.com/prheee) -- **Sam Parsons** - [sam-parsons](https://github.com/sam-parsons) -- **Nancy Dao** - [nancyddao](https://github.com/nancyddao) -- **Evan Grobar** - [egrobar](https://github.com/egrobar) -- **Dan Stein** - [danst3in](https://github.com/danst3in) -- **Amruth Uppaluri** - [amuuth](https://github.com/amuuth) -- **Yoon Choi** - [cyoonique](https://github.com/cyoonique) -- **Nathaniel Adams** - [nathanielBadams](https://github.com/nathanielBadams) -- **Robin Yoong** - [robinyoong](https://github.com/robinyoong) -- **Gary Slootskiy** - [garyslootskiy](https://github.com/garyslootskiy) -- **Sam Haar** - [samhaar](https://github.com/samhaar) -- **Edward Cho** - [edwardcho1231](https://github.com/edwardcho1231) -- **Miguel Gonzalez** - [MigGonzalez](https://github.com/MigGonzalez) -- **Jason Liggayu** - [jasonligg](https://github.com/jasonligg) -- **Warren Tait** - [whtait](https://github.com/whtait) -- **Nathan Fleming** - [njfleming](https://github.com/njfleming) -- **Konrad Kopko** - [konradkop](https://github.com/konradkop) -- **Andrea Li** - [Andrea-gli](https://github.com/Andrea-gli) -- **Paul Ramirez** - [pauleramirez](https://github.com/pauleramirez) -- **TJ Wetmore** - [TWetmore](https://github.com/TWetmore) - -## License - -This project is licensed under the MIT License +

+ +# + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/open-source-labs/Swell/blob/master/LICENSE.txt) +[![Build Status](https://travis-ci.org/open-source-labs/Swell.svg?branch=master)](https://travis-ci.org/open-source-labs/Swell) +![GitHub package.json version](https://img.shields.io/github/package-json/v/open-source-labs/Swell?color=blue) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/getswell/getswell/issues) +[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Swell-%20For%20all%20your%20streaming%20API%20testing%20needs&url=https://www.getswell.io&hashtags=SSE,WebSocket,HTTP,API,developers) + +Swell is a API development tool that enables developers to test endpoints served over streaming technologies including Server-Sent Events (SSE), WebSockets, HTTP2, GraphQL and gRPC. + +## Getting Started + +Visit www.getswell.io to download the latest release. + +Swell is currently available for OS X, Linux and Windows. + +## Highlights + +Swell is a one-stop shop for sending and monitoring your API requests + +- Send and monitor streams over HTTP2 / HTTP1 (including SSEs) and WebSockets +- Create GraphQL queries, introspections, mutations, and subscriptions +- Make API requests based on a range of provided options that conform to the specifications defined in an OpenAPI document. +- Verify STUN and TURN server connectivity for WebRTC applications by generating an SDP +- Provides full streaming testing support for gRPC +- View response timing information and history in an interactive chart for each request +- Save workspaces of multiple requests for later access +- Import and export workspaces for sharing +- Compose test suites in JavaScript with Chai-style TDD/BDD assertion syntax +- Execute a collection of requests in succession and receive clear visual feedback of each test's status +- Schedule requests on to be sent on a regular time interval to support endpoint functional validation tests + +## Supported Technologies + +- _HTTP2_: Swell supports full HTTP2 multiplexing of requests and responses. HTTP requests to the same host will be sent over the same connection. Swell will attempt to initiate an HTTP2 connection for all HTTPS requests by default, but will revert to HTTP1.1 for legacy servers. Multiple concurrent streams are allowed for each connection. + + +- _Server-Sent Events (SSE)_: Initiated by a simple toggle box, Swell displays SSE events one by one as they come in. Similar to HTTP2 streams, multiple open connection streams are allowed for SSE. + + +- _WebSocket (WS)_: Swell enables connecting directly to WebSocket servers with an HTTP handshake. Developers can directly send messages to the connected WS server. Messages are displayed in chatbox format, clearly indicating outgoing and incoming messages. + + +- _GraphQL_: Swell includes full support for all three root types of GraphQL - queries, mutations, and subscriptions as well as Introspection - with and without variables. Smart code editor allows for easy query creation. + + +- _gRPC_: Swell includes full support for all four streaming types of gRPC - unary, client stream, server stream, bidirectional stream. + + +- _OpenAPI_: Swell supports the enumeration and execution of REST and RPC API requests as defined in a user-provided OpenAPI document. + + +- _WebRTC_: Swell enables testing STUN and TURN ICE server connectivity for WebRTC applications. + + + Developers enter ICE server details as an array of JavaScript objects (example code block below). An RTCPeerConnection is instantiated and an SDP is generated. + + ```javascript + [ + { + urls: 'turn:111.222.333.444:54321', + username: 'myAwesomeUsername', + credential: 'mySecretPassword', // or token + credentialType: 'password' + }, + { + urls: 'stun:555.777.888.999:43210', + }, + ] + ``` + +## Additional Features + +- _Scripting in Swell_: Swell allows you to write assertion tests to aid in the test-driven development cycle of backend API services. + +- _Workspaces_: Swell allows you to save workspaces for easier testing of multiple requests. +- _Import/Export Workspaces_: Swell allows you to import and export workspaces, making it easy to share collections with your team. +- _Preview_: You can now view a rendered preview of certain API responses (HTML) + +- _Collection Runner_: You can also stage requests in the workspace and automate the process of sending off each one. No need to manually press send on each one, instead each request will fire off in the order of staging. + +- _Schedule Tests_: You can also automate sending requests to occur on a periodic basis. + + +## Built With + +- Electron +- React +- Redux +- Apollo Client +- Websockets +- gRPC-js +- VM2 +- Chart.js +- Bulma +- IndexedDB +- Chai +- Mocha + +## Authors + +- **Grace Kim** - [gracekiim](https://github.com/gracekiim) +- **Alex Sanhueza** - [alexsanhueza](https://github.com/alexsanhueza) +- **Wyatt Bell** - [wcbell51](https://github.com/wcbell51) +- **John Madrigal** - [johnmadrigal](https://github.com/johnmadrigal) +- **Michael Miller** - [mjmiguel](https://github.com/mjmiguel) +- **Hideaki Aomori** - [h15200](https://github.com/h15200) +- **Matt Gin** - [chinsonhoag](https://github.com/chunsonhoag) +- **Nick Healy** - [nickhealy](http://github.com/nickhealy) +- **Grace Spletzer** - [gspletzer](https://github.com/gspletzer) +- **Stephanie Wood** - [stephwood](https://github.com/stephwood) +- **Anthony Terruso** - [discrete projects](https://github.com/discrete-projects) +- **Brandon Marrero** - [brandon6190](https://github.com/brandon6190) +- **Jason Ou** - [jasonou1994](https://github.com/jasonou1994) +- **Kyle Combs** - [texpatnyc](https://github.com/texpatnyc) +- **Kwadwo Asamoah** - [addoasa](https://github.com/addoasa) +- **Abby Chao** - [abbychao](https://github.com/abbychao) +- **Amanda Flink** - [aflinky](https://github.com/aflinky) +- **Kajol Thapa** - [kajolthapa](https://github.com/kajolthapa) +- **Billy Tran** - [btctrl](https://github.com/btctrl) +- **Paul Rhee** - [prheee](https://github.com/prheee) +- **Sam Parsons** - [sam-parsons](https://github.com/sam-parsons) +- **Nancy Dao** - [nancyddao](https://github.com/nancyddao) +- **Evan Grobar** - [egrobar](https://github.com/egrobar) +- **Dan Stein** - [danst3in](https://github.com/danst3in) +- **Amruth Uppaluri** - [amuuth](https://github.com/amuuth) +- **Yoon Choi** - [cyoonique](https://github.com/cyoonique) +- **Nathaniel Adams** - [nathanielBadams](https://github.com/nathanielBadams) +- **Robin Yoong** - [robinyoong](https://github.com/robinyoong) +- **Gary Slootskiy** - [garyslootskiy](https://github.com/garyslootskiy) +- **Sam Haar** - [samhaar](https://github.com/samhaar) +- **Edward Cho** - [edwardcho1231](https://github.com/edwardcho1231) +- **Miguel Gonzalez** - [MigGonzalez](https://github.com/MigGonzalez) +- **Jason Liggayu** - [jasonligg](https://github.com/jasonligg) +- **Warren Tait** - [whtait](https://github.com/whtait) +- **Nathan Fleming** - [njfleming](https://github.com/njfleming) +- **Konrad Kopko** - [konradkop](https://github.com/konradkop) +- **Andrea Li** - [Andrea-gli](https://github.com/Andrea-gli) +- **Paul Ramirez** - [pauleramirez](https://github.com/pauleramirez) +- **TJ Wetmore** - [TWetmore](https://github.com/TWetmore) +- **Colin Gibson** - [cgefx](https://github.com/cgefx) +- **Ted Craig** - [tedcraig](https://github.com/tedcraig) +- **Anthony Wong** - [awong428](https://github.com/awong428) +- **John Jongsun Suh** - [MajorLift](https://github.com/MajorLift) + +## License + +This project is licensed under the MIT License diff --git a/ReadMeGifs/Gifs/openapi.gif b/ReadMeGifs/Gifs/openapi.gif new file mode 100644 index 000000000..3fec007d3 Binary files /dev/null and b/ReadMeGifs/Gifs/openapi.gif differ diff --git a/ReadMeGifs/Gifs/webrtc.gif b/ReadMeGifs/Gifs/webrtc.gif new file mode 100644 index 000000000..5ae0ec956 Binary files /dev/null and b/ReadMeGifs/Gifs/webrtc.gif differ diff --git a/__mocks__/electronMock.js b/__mocks__/electronMock.js index 8a710363c..efd88978c 100644 --- a/__mocks__/electronMock.js +++ b/__mocks__/electronMock.js @@ -1,3 +1,3 @@ -export const ipcRenderer = { - on: jest.fn(), -}; +export const ipcRenderer = { + on: jest.fn(), +}; diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js index 215233e96..06ea282e8 100644 --- a/__mocks__/fileMock.js +++ b/__mocks__/fileMock.js @@ -1,2 +1,2 @@ -// see jest.config -module.exports = "test-file-stub"; +// see jest.config +module.exports = 'test-file-stub'; diff --git a/__mocks__/styleMocks.js b/__mocks__/styleMocks.js index ab581be3e..dfb71aa63 100644 --- a/__mocks__/styleMocks.js +++ b/__mocks__/styleMocks.js @@ -1,3 +1,3 @@ -// see jest.config - -module.exports = {}; +// see jest.config + +module.exports = {}; diff --git a/__tests__/__snapshots__/responseTests.js.snap b/__tests__/__snapshots__/responseTests.js.snap deleted file mode 100644 index 63aac4f8c..000000000 --- a/__tests__/__snapshots__/responseTests.js.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResponseSubscriptionDisplay should initialize as listening 1`] = ` -
-
- Listening for new data -
-
-`; diff --git a/__tests__/businessReducer.js b/__tests__/businessReducer.js index 2166ba471..5b5c5a770 100644 --- a/__tests__/businessReducer.js +++ b/__tests__/businessReducer.js @@ -1,603 +1,635 @@ -import reducer from "../src/client/reducers/business"; - -describe("Business reducer", () => { - let state; - - beforeEach(() => { - state = { - currentTab: "First Tab", - reqResArray: [], - history: [], - collections: [], - warningMessage: {}, - newRequestFields: { - protocol: "", - restUrl: "http://", - wsUrl: "ws://", - gqlUrl: "https://", - grpcUrl: "", - url: "http://", - method: "GET", - graphQL: false, - gRPC: false, - network: "rest", - }, - newRequestHeaders: { - headersArr: [], - count: 0, - }, - newRequestStreams: { - streamsArr: [], - count: 0, - streamContent: [], - selectedPackage: null, - selectedRequest: null, - selectedService: null, - selectedServiceObj: null, - selectedStreamingType: null, - initialQuery: null, - queryArr: null, - protoPath: null, - services: null, - protoContent: "", - }, - newRequestCookies: { - cookiesArr: [], - count: 0, - }, - newRequestBody: { - bodyContent: "", - bodyVariables: "", - bodyType: "raw", - rawType: "text/plain", - JSONFormatted: true, - bodyIsNew: false, - }, - newRequestSSE: { - isSSE: false, - }, - introspectionData: { schemaSDL: null, clientSchema: null }, - dataPoints: {}, - currentResponse: { - request: { - network: "", - }, - }, - }; - }); - - describe("default state", () => { - it("should return a default state when given an undefined input", () => { - expect(reducer(undefined, { type: undefined })).toEqual(state); - }); - }); - - describe("unrecognized action types", () => { - it("should return the original without any duplication", () => { - const action = { type: "aajsbicawlbejckr" }; - expect(reducer(state, action)).toBe(state); - }); - }); - - describe("GET_HISTORY", () => { - const fakeHistory = [ - { - date: "02/15/2019", - history: [ - { - id: "d79d8f1a-f53c-41a1-a7e3-514f9f5cf24e", - created_at: "2019-02-15T21:40:44.132Z", - }, - { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-15T20:52:35.990Z", - }, - ], - }, - { - date: "02/14/2019", - history: [ - { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2019-02-15T00:40:56.360Z", - }, - { - id: "577eab93-e707-4dc0-af45-7adcc78807fa", - created_at: "2019-02-15T00:16:56.133Z", - }, - ], - }, - ]; - - const action = { - type: "GET_HISTORY", - payload: fakeHistory, - }; - - it("should update history in state", () => { - const { reqResArray, history } = reducer(state, action); - expect(reqResArray).toEqual([]); - expect(history).toEqual(fakeHistory); - }); - }); - - describe("DELETE_HISTORY", () => { - const fakeHistory = [ - { - date: "02/15/2019", - history: [ - { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-15T20:52:35.990Z", - }, - ], - }, - { - date: "02/14/2019", - history: [ - { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2019-02-15T00:40:56.360Z", - }, - { - id: "577eab93-e707-4dc0-af45-7adcc78807fa", - created_at: "2019-02-15T00:16:56.133Z", - }, - ], - }, - ]; - - beforeEach(() => { - state.history = fakeHistory; - }); - - it("should delete the proper history", () => { - const action = { - type: "DELETE_HISTORY", - payload: { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2019-02-15T00:40:56.360Z", - }, - }; - - const { history } = reducer(state, action); - expect(history).not.toBe(fakeHistory); - expect(history[1].history.length).toEqual(1); - expect(history[1].history[0].id).toBe( - "577eab93-e707-4dc0-af45-7adcc78807fa" - ); - }); - - it("should remove date array if empty", () => { - const action = { - type: "DELETE_HISTORY", - payload: { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-15T20:52:35.990Z", - }, - }; - const initialHistory = state.history; - const { history } = reducer(state, action); - expect(history).not.toBe(initialHistory); - expect(initialHistory.length).toBe(2); - expect(history.length).toBe(1); - }); - }); - - describe("REQRES_CLEAR", () => { - const action = { - type: "REQRES_CLEAR", - }; - - it("should empty the reqResArray", () => { - const initialReqResArray = [{ first: 1 }, { second: 2 }]; - // state.reqResArray = initialReqResArray; - // expect(state.reqResArray).toBe(initialReqResArray); //unnecessary...? - const { reqResArray } = reducer(state, action); - expect(reqResArray).not.toBe(initialReqResArray); - expect(reqResArray).toEqual([]); - }); - }); - - describe("REQRES_ADD", () => { - const fakeReqRes1 = { - id: "d79d8f1a-f53c-41a1-a7e3-514f9f5cf24e", - created_at: "2019-02-15T21:40:44.132Z", - protocol: "http://", - request: { method: "POST", body: "I am a request body" }, - response: {}, - }; - - const fakeReqRes2 = { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-16T20:52:35.990Z", - protocol: "http://", - request: { method: "POST", body: "I am a newer request body" }, - response: {}, - }; - - const action1 = { - type: "REQRES_ADD", - payload: fakeReqRes1, - }; - - const action2 = { - type: "REQRES_ADD", - payload: fakeReqRes2, - }; - - it("should add the reqRes to reqResArray", () => { - const initialReqResArray = state.reqResArray; - const { reqResArray } = reducer(state, action1); - expect(reqResArray).not.toEqual(initialReqResArray); - expect(reqResArray.length).toEqual(1); - expect(reqResArray[0]).toEqual(fakeReqRes1); - expect(reqResArray[0].request.body).toEqual("I am a request body"); - }); - - it("should add the reqRes to the history", () => { - const firstState = reducer(state, action1); - expect(firstState.history.length).toEqual(1); - const { history } = reducer(firstState, action2); - expect(history.length).toEqual(2); - expect(history[0].date).toEqual("02/16/2019"); - expect(history[1].date).toEqual("02/15/2019"); - }); - }); - - describe("REQRES_DELETE", () => { - const fakeReqResArray = [ - { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-15T20:52:35.990Z", - }, - { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2019-02-15T00:40:56.360Z", - }, - { - id: "577eab93-e707-4dc0-af45-7adcc78807fa", - created_at: "2019-02-15T00:16:56.133Z", - }, - ]; - - const action = { - type: "REQRES_DELETE", - payload: fakeReqResArray[1], - }; - - it("should delete a reqRes from reqResArray", () => { - const initialReqResArray = state.reqResArray; - state.reqResArray = fakeReqResArray; - const { reqResArray } = reducer(state, action); - expect(reqResArray).not.toBe(initialReqResArray); - expect(reqResArray.length).toEqual(2); - expect(reqResArray[0]).toEqual(fakeReqResArray[0]); - expect(reqResArray[1]).toEqual(fakeReqResArray[2]); - }); - }); - - describe("REQRES_UPDATE", () => { - const fakeReqResArray = [ - { - id: "c8d73eec-e383-4735-943a-20deab42ecff", - created_at: "2019-02-15T20:52:35.990Z", - }, - { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2019-02-15T00:40:56.360Z", - }, - { - id: "577eab93-e707-4dc0-af45-7adcc78807fa", - created_at: "2019-02-15T00:16:56.133Z", - }, - ]; - - const action = { - type: "REQRES_UPDATE", - payload: { - id: "0faf2207-20d3-4f62-98ca-51a39c8c15dd", - created_at: "2018-02-15T00:40:56.360Z", - newKey: "this is a new value", - }, - }; - - it("should update a reqRes from reqResArray", () => { - const initialReqResArray = state.reqResArray; - state.reqResArray = fakeReqResArray; - const { reqResArray } = reducer(state, action); - expect(reqResArray).not.toBe(initialReqResArray); - expect(reqResArray.length).toEqual(3); - expect(reqResArray[1]).toEqual(action.payload); - expect(reqResArray[0]).toEqual(fakeReqResArray[0]); - expect(reqResArray[2]).toEqual(fakeReqResArray[2]); - }); - }); - - describe("SET_COMPOSER_WARNING_MESSAGE", () => { - const action = { - type: "SET_COMPOSER_WARNING_MESSAGE", - payload: "WARNING! TESTING IN PROGRESS!", - }; - - it("should update the warningMessage", () => { - const initialMessage = state.warningMessage; - const { warningMessage } = reducer(state, action); - expect(warningMessage).not.toEqual(initialMessage); - expect(warningMessage).toEqual(action.payload); - }); - }); - - describe("SET_NEW_REQUEST_FIELDS", () => { - //alternates url and tests all 5 http/s methods and 3 gql types - const getAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "GET", - protocol: "", - url: "http://www.fakesite.com", - graphQL: false, - }, - }; - const postAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "POST", - protocol: "", - url: "https://www.fakesite.com", - graphQL: false, - }, - }; - const putAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "PUT", - protocol: "", - url: "http://www.fakesite.com", - graphQL: false, - }, - }; - const patchAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "PATCH", - protocol: "", - url: "https://www.fakesite.com", - graphQL: false, - }, - }; - const deleteAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "DELETE", - protocol: "", - url: "http://www.fakesite.com", - graphQL: false, - }, - }; - const queryAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "QUERY", - protocol: "", - url: "https://www.fakesite.com", - graphQL: true, - }, - }; - const mutationAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "MUTATION", - protocol: "", - url: "http://www.fakesite.com", - graphQL: true, - }, - }; - const subscriptionAction = { - type: "SET_NEW_REQUEST_FIELDS", - payload: { - method: "SUBSCRIPTION", - protocol: "", - url: "https://www.fakesite.com", - graphQL: true, - }, - }; - const requestStreamsAction = { - type: "SET_NEW_REQUEST_STREAMS", - payload: { - streamsArr: [], - count: 0, - streamContent: [], - selectedPackage: "helloworld", - selectedRequest: "helloRequest", - selectedService: "hello", - selectedStreamingType: null, - initialQuery: null, - queryArr: null, - protoPath: null, - services: null, - }, - }; - it("sets the newRequestStreams on SET_NEW_REQUEST_STREAMS", () => { - const { newRequestStreams } = reducer(state, requestStreamsAction); - expect(newRequestStreams).toEqual(requestStreamsAction.payload); - }); - it("sets the newRequestFields on POST", () => { - const { newRequestFields } = reducer(state, postAction); - expect(newRequestFields).toEqual(postAction.payload); - }); - it("sets the newRequestFields on GET", () => { - const { newRequestFields } = reducer(state, getAction); - expect(newRequestFields).toEqual(getAction.payload); - }); - it("sets the newRequestFields on PUT", () => { - const { newRequestFields } = reducer(state, putAction); - expect(newRequestFields).toEqual(putAction.payload); - }); - it("sets the newRequestFields on PATCH", () => { - const { newRequestFields } = reducer(state, patchAction); - expect(newRequestFields).toEqual(patchAction.payload); - }); - it("sets the newRequestFields on DELETE", () => { - const { newRequestFields } = reducer(state, deleteAction); - expect(newRequestFields).toEqual(deleteAction.payload); - }); - it("sets the newRequestFields on QUERY", () => { - const { newRequestFields } = reducer(state, queryAction); - expect(newRequestFields).toEqual(queryAction.payload); - }); - it("sets the newRequestFields on MUTATION", () => { - const { newRequestFields } = reducer(state, mutationAction); - expect(newRequestFields).toEqual(mutationAction.payload); - }); - it("sets the newRequestFields on SUBSCRIPTION", () => { - const { newRequestFields } = reducer(state, subscriptionAction); - expect(newRequestFields).toEqual(subscriptionAction.payload); - }); - }); - - describe("SET_NEW_REQUEST_HEADERS", () => { - const contentTypeHeaderAction = { - type: "SET_NEW_REQUEST_HEADERS", - payload: { - headersArr: [ - { - id: 0, - active: true, - key: "content-type", - value: "application/json", - }, - ], - override: false, - count: [ - { active: true, key: "content-type", value: "application/json" }, - ].length, - }, - }; - const otherHeaderAction = { - type: "SET_NEW_REQUEST_HEADERS", - payload: { - headersArr: [ - { - id: 0, - active: true, - key: "content-type", - value: "application/json", - }, - { - id: 1, - active: true, - key: "otherHeader", - value: "otherHeaderValue", - }, - ], - override: false, - count: [ - { - id: 0, - active: true, - key: "content-type", - value: "application/json", - }, - { - id: 1, - active: true, - key: "otherHeader", - value: "otherHeaderValue", - }, - ].length, - }, - }; - - it("sets new requestHeaders", () => { - const { newRequestHeaders } = reducer(state, contentTypeHeaderAction); - expect(newRequestHeaders.headersArr.length).toBe(1); - expect(newRequestHeaders.headersArr[0]).toEqual( - contentTypeHeaderAction.payload.headersArr[0] - ); - expect(newRequestHeaders.count).toBe(1); - expect(newRequestHeaders.override).toBe(false); - }); - it("can set multiple requestHeaders", () => { - const { newRequestHeaders } = reducer(state, otherHeaderAction); - expect(newRequestHeaders.headersArr.length).toBe(2); - expect(newRequestHeaders.headersArr[1]).toEqual( - otherHeaderAction.payload.headersArr[1] - ); - expect(newRequestHeaders.count).toBe(2); - expect(newRequestHeaders.override).toBe(false); - }); - }); - - describe("SET_NEW_REQUEST_BODY", () => { - const action = { - type: "SET_NEW_REQUEST_BODY", - payload: { - bodyContent: '{ "key": "value"}', - bodyVariables: "", - bodyType: "raw", - rawType: "application/json", - JSONFormatted: true, - }, - }; - - it("sets new requestBody", () => { - const { newRequestBody } = reducer(state, action); - expect(newRequestBody).toEqual(action.payload); - expect(typeof newRequestBody.bodyContent).toBe("string"); - expect(typeof newRequestBody.JSONFormatted).toBe("boolean"); - }); - }); - - describe("SET_NEW_REQUEST_COOKIES", () => { - const cookieAction = { - type: "SET_NEW_REQUEST_COOKIES", - payload: { - cookiesArr: [{ key: "admin", value: "password" }], - count: [{ key: "admin", value: "password" }].length, - }, - }; - const otherCookieAction = { - type: "SET_NEW_REQUEST_COOKIES", - payload: { - cookiesArr: [ - { key: "admin", value: "password" }, - { key: "admin2", value: "password2" }, - ], - count: [ - { key: "admin", value: "password" }, - { key: "admin2", value: "password2" }, - ].length, - }, - }; - - it("sets new requestCookies", () => { - const { newRequestCookies } = reducer(state, cookieAction); - expect(newRequestCookies.cookiesArr.length).toBe(1); - expect(newRequestCookies.cookiesArr[0]).toEqual( - cookieAction.payload.cookiesArr[0] - ); - expect(newRequestCookies.count).toBe(1); - }); - it("can set multiple requestCookies", () => { - const { newRequestCookies } = reducer(state, otherCookieAction); - expect(newRequestCookies.cookiesArr.length).toBe(2); - expect(newRequestCookies.cookiesArr[1]).toEqual( - otherCookieAction.payload.cookiesArr[1] - ); - expect(newRequestCookies.count).toBe(2); - }); - }); - - describe("SET_CURRENT_TAB", () => { - const action = { - type: "SET_CURRENT_TAB", - payload: "Second Tab", - }; - - it("should update currentTab", () => { - const { currentTab } = reducer(state, action); - expect(currentTab).toEqual(action.payload); - }); - }); -}); +import reducer from '../src/client/reducers/business'; + +describe('Business reducer', () => { + let state; + beforeEach(() => { + state = { + currentTab: 'First Tab', + reqResArray: [], + scheduledReqResArray: [], + history: [], + collections: [], + warningMessage: {}, + newRequestsOpenAPI: { + openapiMetadata: { + info: {}, + tags: [], + serverUrls: [], + }, + openapiReqArray: [], + }, + newRequestFields: { + protocol: '', + restUrl: 'http://', + wsUrl: 'ws://', + gqlUrl: 'https://', + grpcUrl: '', + webrtcUrl: '', + url: 'http://', + method: 'GET', + graphQL: false, + gRPC: false, + ws: false, + openapi: false, + webrtc: false, + network: 'rest', + testContent: '', + testResults: [], + openapiReqObj: {}, + }, + newRequestHeaders: { + headersArr: [], + count: 0, + }, + newRequestStreams: { + streamsArr: [], + count: 0, + streamContent: [], + selectedPackage: null, + selectedRequest: null, + selectedService: null, + selectedServiceObj: null, + selectedStreamingType: null, + initialQuery: null, + queryArr: null, + protoPath: null, + services: null, + protoContent: '', + }, + newRequestCookies: { + cookiesArr: [], + count: 0, + }, + newRequestBody: { + bodyContent: '', + bodyVariables: '', + bodyType: 'raw', + rawType: 'text/plain', + JSONFormatted: true, + bodyIsNew: false, + }, + newRequestSSE: { + isSSE: false, + }, + newRequestOpenAPIObject: { + request: { + id: 0, + enabled: true, + reqTags: [], + reqServers: [], + summary: '', + description: '', + operationId: '', + method: '', + endpoint: '', + headers: {}, + parameters: [], + body: new Map(), + urls: [], + }, + }, + introspectionData: { schemaSDL: null, clientSchema: null }, + dataPoints: {}, + currentResponse: { + request: { + network: '', + }, + }, + }; + }); + + describe('default state', () => { + it('should return a default state when given an undefined input', () => { + expect(reducer(undefined, { type: undefined })).toEqual(state); + }); + }); + + describe('unrecognized action types', () => { + it('should return the original without any duplication', () => { + const action = { type: 'aajsbicawlbejckr' }; + expect(reducer(state, action)).toBe(state); + }); + }); + + describe('GET_HISTORY', () => { + const fakeHistory = [ + { + date: '02/15/2019', + history: [ + { + id: 'd79d8f1a-f53c-41a1-a7e3-514f9f5cf24e', + created_at: '2019-02-15T21:40:44.132Z', + }, + { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-15T20:52:35.990Z', + }, + ], + }, + { + date: '02/14/2019', + history: [ + { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2019-02-15T00:40:56.360Z', + }, + { + id: '577eab93-e707-4dc0-af45-7adcc78807fa', + created_at: '2019-02-15T00:16:56.133Z', + }, + ], + }, + ]; + + const action = { + type: 'GET_HISTORY', + payload: fakeHistory, + }; + + it('should update history in state', () => { + const { reqResArray, history } = reducer(state, action); + expect(reqResArray).toEqual([]); + expect(history).toEqual(fakeHistory); + }); + }); + + describe('DELETE_HISTORY', () => { + const fakeHistory = [ + { + date: '02/15/2019', + history: [ + { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-15T20:52:35.990Z', + }, + ], + }, + { + date: '02/14/2019', + history: [ + { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2019-02-15T00:40:56.360Z', + }, + { + id: '577eab93-e707-4dc0-af45-7adcc78807fa', + created_at: '2019-02-15T00:16:56.133Z', + }, + ], + }, + ]; + + beforeEach(() => { + state.history = fakeHistory; + }); + + it('should delete the proper history', () => { + const action = { + type: 'DELETE_HISTORY', + payload: { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2019-02-15T00:40:56.360Z', + }, + }; + + const { history } = reducer(state, action); + expect(history).not.toBe(fakeHistory); + expect(history[1].history.length).toEqual(1); + expect(history[1].history[0].id).toBe( + '577eab93-e707-4dc0-af45-7adcc78807fa' + ); + }); + + it('should remove date array if empty', () => { + const action = { + type: 'DELETE_HISTORY', + payload: { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-15T20:52:35.990Z', + }, + }; + const initialHistory = state.history; + const { history } = reducer(state, action); + expect(history).not.toBe(initialHistory); + expect(initialHistory.length).toBe(2); + expect(history.length).toBe(1); + }); + }); + + describe('REQRES_CLEAR', () => { + const action = { + type: 'REQRES_CLEAR', + }; + + it('should empty the reqResArray', () => { + const initialReqResArray = [{ first: 1 }, { second: 2 }]; + // state.reqResArray = initialReqResArray; + // expect(state.reqResArray).toBe(initialReqResArray); //unnecessary...? + const { reqResArray } = reducer(state, action); + expect(reqResArray).not.toBe(initialReqResArray); + expect(reqResArray).toEqual([]); + }); + }); + + describe('REQRES_ADD', () => { + const fakeReqRes1 = { + id: 'd79d8f1a-f53c-41a1-a7e3-514f9f5cf24e', + created_at: '2019-02-15T21:40:44.132Z', + protocol: 'http://', + request: { method: 'POST', body: 'I am a request body' }, + response: {}, + }; + + const fakeReqRes2 = { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-16T20:52:35.990Z', + protocol: 'http://', + request: { method: 'POST', body: 'I am a newer request body' }, + response: {}, + }; + + const action1 = { + type: 'REQRES_ADD', + payload: fakeReqRes1, + }; + + const action2 = { + type: 'REQRES_ADD', + payload: fakeReqRes2, + }; + + it('should add the reqRes to reqResArray', () => { + const initialReqResArray = state.reqResArray; + const { reqResArray } = reducer(state, action1); + expect(reqResArray).not.toEqual(initialReqResArray); + expect(reqResArray.length).toEqual(1); + expect(reqResArray[0]).toEqual(fakeReqRes1); + expect(reqResArray[0].request.body).toEqual('I am a request body'); + }); + + it('should add the reqRes to the history', () => { + const firstState = reducer(state, action1); + expect(firstState.history.length).toEqual(1); + const { history } = reducer(firstState, action2); + expect(history.length).toEqual(2); + expect(history[0].date).toEqual('02/16/2019'); + expect(history[1].date).toEqual('02/15/2019'); + }); + }); + + describe('REQRES_DELETE', () => { + const fakeReqResArray = [ + { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-15T20:52:35.990Z', + }, + { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2019-02-15T00:40:56.360Z', + }, + { + id: '577eab93-e707-4dc0-af45-7adcc78807fa', + created_at: '2019-02-15T00:16:56.133Z', + }, + ]; + + const action = { + type: 'REQRES_DELETE', + payload: fakeReqResArray[1], + }; + + it('should delete a reqRes from reqResArray', () => { + const initialReqResArray = state.reqResArray; + state.reqResArray = fakeReqResArray; + const { reqResArray } = reducer(state, action); + expect(reqResArray).not.toBe(initialReqResArray); + expect(reqResArray.length).toEqual(2); + expect(reqResArray[0]).toEqual(fakeReqResArray[0]); + expect(reqResArray[1]).toEqual(fakeReqResArray[2]); + }); + }); + + describe('REQRES_UPDATE', () => { + const fakeReqResArray = [ + { + id: 'c8d73eec-e383-4735-943a-20deab42ecff', + created_at: '2019-02-15T20:52:35.990Z', + }, + { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2019-02-15T00:40:56.360Z', + }, + { + id: '577eab93-e707-4dc0-af45-7adcc78807fa', + created_at: '2019-02-15T00:16:56.133Z', + }, + ]; + + const action = { + type: 'REQRES_UPDATE', + payload: { + id: '0faf2207-20d3-4f62-98ca-51a39c8c15dd', + created_at: '2018-02-15T00:40:56.360Z', + newKey: 'this is a new value', + }, + }; + + it('should update a reqRes from reqResArray', () => { + const initialReqResArray = state.reqResArray; + state.reqResArray = fakeReqResArray; + const { reqResArray } = reducer(state, action); + expect(reqResArray).not.toBe(initialReqResArray); + expect(reqResArray.length).toEqual(3); + expect(reqResArray[1]).toEqual(action.payload); + expect(reqResArray[0]).toEqual(fakeReqResArray[0]); + expect(reqResArray[2]).toEqual(fakeReqResArray[2]); + }); + }); + + describe('SET_COMPOSER_WARNING_MESSAGE', () => { + const action = { + type: 'SET_COMPOSER_WARNING_MESSAGE', + payload: 'WARNING! TESTING IN PROGRESS!', + }; + + it('should update the warningMessage', () => { + const initialMessage = state.warningMessage; + const { warningMessage } = reducer(state, action); + expect(warningMessage).not.toEqual(initialMessage); + expect(warningMessage).toEqual(action.payload); + }); + }); + + describe('SET_NEW_REQUEST_FIELDS', () => { + // alternates url and tests all 5 http/s methods and 3 gql types + const getAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'GET', + protocol: '', + url: 'http://www.fakesite.com', + graphQL: false, + }, + }; + const postAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'POST', + protocol: '', + url: 'https://www.fakesite.com', + graphQL: false, + }, + }; + const putAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'PUT', + protocol: '', + url: 'http://www.fakesite.com', + graphQL: false, + }, + }; + const patchAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'PATCH', + protocol: '', + url: 'https://www.fakesite.com', + graphQL: false, + }, + }; + const deleteAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'DELETE', + protocol: '', + url: 'http://www.fakesite.com', + graphQL: false, + }, + }; + const queryAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'QUERY', + protocol: '', + url: 'https://www.fakesite.com', + graphQL: true, + }, + }; + const mutationAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'MUTATION', + protocol: '', + url: 'http://www.fakesite.com', + graphQL: true, + }, + }; + const subscriptionAction = { + type: 'SET_NEW_REQUEST_FIELDS', + payload: { + method: 'SUBSCRIPTION', + protocol: '', + url: 'https://www.fakesite.com', + graphQL: true, + }, + }; + const requestStreamsAction = { + type: 'SET_NEW_REQUEST_STREAMS', + payload: { + streamsArr: [], + count: 0, + streamContent: [], + selectedPackage: 'helloworld', + selectedRequest: 'helloRequest', + selectedService: 'hello', + selectedStreamingType: null, + initialQuery: null, + queryArr: null, + protoPath: null, + services: null, + }, + }; + it('sets the newRequestStreams on SET_NEW_REQUEST_STREAMS', () => { + const { newRequestStreams } = reducer(state, requestStreamsAction); + expect(newRequestStreams).toEqual(requestStreamsAction.payload); + }); + it('sets the newRequestFields on POST', () => { + const { newRequestFields } = reducer(state, postAction); + expect(newRequestFields).toEqual(postAction.payload); + }); + it('sets the newRequestFields on GET', () => { + const { newRequestFields } = reducer(state, getAction); + expect(newRequestFields).toEqual(getAction.payload); + }); + it('sets the newRequestFields on PUT', () => { + const { newRequestFields } = reducer(state, putAction); + expect(newRequestFields).toEqual(putAction.payload); + }); + it('sets the newRequestFields on PATCH', () => { + const { newRequestFields } = reducer(state, patchAction); + expect(newRequestFields).toEqual(patchAction.payload); + }); + it('sets the newRequestFields on DELETE', () => { + const { newRequestFields } = reducer(state, deleteAction); + expect(newRequestFields).toEqual(deleteAction.payload); + }); + it('sets the newRequestFields on QUERY', () => { + const { newRequestFields } = reducer(state, queryAction); + expect(newRequestFields).toEqual(queryAction.payload); + }); + it('sets the newRequestFields on MUTATION', () => { + const { newRequestFields } = reducer(state, mutationAction); + expect(newRequestFields).toEqual(mutationAction.payload); + }); + it('sets the newRequestFields on SUBSCRIPTION', () => { + const { newRequestFields } = reducer(state, subscriptionAction); + expect(newRequestFields).toEqual(subscriptionAction.payload); + }); + }); + + describe('SET_NEW_REQUEST_HEADERS', () => { + const contentTypeHeaderAction = { + type: 'SET_NEW_REQUEST_HEADERS', + payload: { + headersArr: [ + { + id: 0, + active: true, + key: 'content-type', + value: 'application/json', + }, + ], + override: false, + count: [ + { active: true, key: 'content-type', value: 'application/json' }, + ].length, + }, + }; + const otherHeaderAction = { + type: 'SET_NEW_REQUEST_HEADERS', + payload: { + headersArr: [ + { + id: 0, + active: true, + key: 'content-type', + value: 'application/json', + }, + { + id: 1, + active: true, + key: 'otherHeader', + value: 'otherHeaderValue', + }, + ], + override: false, + count: [ + { + id: 0, + active: true, + key: 'content-type', + value: 'application/json', + }, + { + id: 1, + active: true, + key: 'otherHeader', + value: 'otherHeaderValue', + }, + ].length, + }, + }; + + it('sets new requestHeaders', () => { + const { newRequestHeaders } = reducer(state, contentTypeHeaderAction); + expect(newRequestHeaders.headersArr.length).toBe(1); + expect(newRequestHeaders.headersArr[0]).toEqual( + contentTypeHeaderAction.payload.headersArr[0] + ); + expect(newRequestHeaders.count).toBe(1); + expect(newRequestHeaders.override).toBe(false); + }); + it('can set multiple requestHeaders', () => { + const { newRequestHeaders } = reducer(state, otherHeaderAction); + expect(newRequestHeaders.headersArr.length).toBe(2); + expect(newRequestHeaders.headersArr[1]).toEqual( + otherHeaderAction.payload.headersArr[1] + ); + expect(newRequestHeaders.count).toBe(2); + expect(newRequestHeaders.override).toBe(false); + }); + }); + + describe('SET_NEW_REQUEST_BODY', () => { + const action = { + type: 'SET_NEW_REQUEST_BODY', + payload: { + bodyContent: '{ "key": "value"}', + bodyVariables: '', + bodyType: 'raw', + rawType: 'application/json', + JSONFormatted: true, + }, + }; + + it('sets new requestBody', () => { + const { newRequestBody } = reducer(state, action); + expect(newRequestBody).toEqual(action.payload); + expect(typeof newRequestBody.bodyContent).toBe('string'); + expect(typeof newRequestBody.JSONFormatted).toBe('boolean'); + }); + }); + + describe('SET_NEW_REQUEST_COOKIES', () => { + const cookieAction = { + type: 'SET_NEW_REQUEST_COOKIES', + payload: { + cookiesArr: [{ key: 'admin', value: 'password' }], + count: [{ key: 'admin', value: 'password' }].length, + }, + }; + const otherCookieAction = { + type: 'SET_NEW_REQUEST_COOKIES', + payload: { + cookiesArr: [ + { key: 'admin', value: 'password' }, + { key: 'admin2', value: 'password2' }, + ], + count: [ + { key: 'admin', value: 'password' }, + { key: 'admin2', value: 'password2' }, + ].length, + }, + }; + + it('sets new requestCookies', () => { + const { newRequestCookies } = reducer(state, cookieAction); + expect(newRequestCookies.cookiesArr.length).toBe(1); + expect(newRequestCookies.cookiesArr[0]).toEqual( + cookieAction.payload.cookiesArr[0] + ); + expect(newRequestCookies.count).toBe(1); + }); + it('can set multiple requestCookies', () => { + const { newRequestCookies } = reducer(state, otherCookieAction); + expect(newRequestCookies.cookiesArr.length).toBe(2); + expect(newRequestCookies.cookiesArr[1]).toEqual( + otherCookieAction.payload.cookiesArr[1] + ); + expect(newRequestCookies.count).toBe(2); + }); + }); + + describe('SET_CURRENT_TAB', () => { + const action = { + type: 'SET_CURRENT_TAB', + payload: 'Second Tab', + }; + + it('should update currentTab', () => { + const { currentTab } = reducer(state, action); + expect(currentTab).toEqual(action.payload); + }); + }); +}); diff --git a/__tests__/composerTests.js b/__tests__/composerTests.js index 60f594a86..a9a03b1fd 100644 --- a/__tests__/composerTests.js +++ b/__tests__/composerTests.js @@ -1,54 +1,55 @@ -import React from "react"; -import { configure, shallow } from "enzyme"; -import Adapter from "enzyme-adapter-react-16"; -import ProtocolSelect from "../src/client/components/composer/NewRequest/ProtocolSelect.jsx"; - -configure({ adapter: new Adapter() }); - -describe("GraphQL Composer", () => { - const state = { - currentTab: "First Tab", - reqResArray: [], - history: [], - warningMessage: "", - newRequestFields: { - method: "GET", - protocol: "", - url: "", - graphQL: false, - }, - newRequestHeaders: { - headersArr: [], - count: 0, - }, - newRequestCookies: { - cookiesArr: [], - count: 0, - }, - newRequestBody: { - bodyContent: "", - bodyType: "raw", - rawType: "Text (text/plain)", - JSONFormatted: true, - bodyVariables: "", - }, - }; - describe("Setting GQL fields, headers, and body", () => { - describe("ProtocolSelect", () => { - let wrapper; - const props = { - currentProtocol: "", - onChangeHandler: jest.fn(), - graphQL: false, - }; - - beforeAll(() => { - wrapper = shallow(); - }); - - it("Renders a
", () => { - expect(wrapper.type()).toEqual("div"); - }); - }); - }); -}); +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { configure, shallow } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import ProtocolSelect from '../src/client/components/composer/NewRequest/ProtocolSelect.jsx'; + +configure({ adapter: new Adapter() }); + +describe('GraphQL Composer', () => { + const state = { + currentTab: 'First Tab', + reqResArray: [], + history: [], + warningMessage: '', + newRequestFields: { + method: 'GET', + protocol: '', + url: '', + graphQL: false, + }, + newRequestHeaders: { + headersArr: [], + count: 0, + }, + newRequestCookies: { + cookiesArr: [], + count: 0, + }, + newRequestBody: { + bodyContent: '', + bodyType: 'raw', + rawType: 'Text (text/plain)', + JSONFormatted: true, + bodyVariables: '', + }, + }; + describe('Setting GQL fields, headers, and body', () => { + describe('ProtocolSelect', () => { + let wrapper; + const props = { + currentProtocol: '', + onChangeHandler: jest.fn(), + graphQL: false, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('Renders a
', () => { + expect(wrapper.type()).toEqual('div'); + }); + }); + }); +}); diff --git a/__tests__/dbTests.js b/__tests__/dbTests.js index 42e8d1562..b4f1c722e 100644 --- a/__tests__/dbTests.js +++ b/__tests__/dbTests.js @@ -1,56 +1,56 @@ -// started dexie testing set up. -// I would suggest continue TDD by writing some collection tests, then -// importing the actual controllers and testing directlyerly - -// const collectionsController = require("../src/client/controllers/collectionsController"); -// const historyController = require("../src/client/controllers/historyController"); - -const Dexie = require("dexie"); - -// https://stackoverflow.com/questions/47934383/indexeddb-testing-with-jest-enzyme-referenceerror-indexeddb-is-not-defined -Dexie.dependencies.indexedDB = require("fake-indexeddb"); -Dexie.dependencies.IDBKeyRange = require("fake-indexeddb/lib/FDBKeyRange"); - -const db = new Dexie("test"); - -db.version(2).stores({ - history: "id, created_at", - collections: "id, created_at, &name", -}); - -db.version(1).stores({ - history: "id, created_at", -}); - -// now we have db.history and db.collections - -// for setup and teardown tasks that are asynchronous, take care to RETURN the promise -describe("db test", () => { - beforeEach(() => db.history.clear().catch((err) => console.log(err))); - afterEach(() => db.history.clear().catch((err) => console.log(err))); - describe("history tests", () => { - it("can add history with id", async () => { - await db.history.put({ id: 8 }); - const count = await db.history.count(); - const arr = await db.history.toArray(); - expect(count).toEqual(1); - expect(arr[0].id).toEqual(8); - }); - - it("will not add history with an empty object", async () => { - db.history.put({}).catch((err) => expect(err.name).toEqual("DataError")); - const count = await db.history.count(); - expect(count).toEqual(0); - }); - - it("will not add history without an id", async () => { - await db.history - .put({ created_at: Date.now() }) - .catch((err) => expect(err.name).toEqual("DataError")); - const count = await db.history.count(); - expect(count).toEqual(0); - }); - }); -}); -// describe("collection tests", () => {}); -// }); +// started dexie testing set up. +// I would suggest continue TDD by writing some collection tests, then +// importing the actual controllers and testing directlyerly + +// const collectionsController = require("../src/client/controllers/collectionsController"); +// const historyController = require("../src/client/controllers/historyController"); + +const Dexie = require('dexie'); + +// https://stackoverflow.com/questions/47934383/indexeddb-testing-with-jest-enzyme-referenceerror-indexeddb-is-not-defined +Dexie.dependencies.indexedDB = require('fake-indexeddb'); +Dexie.dependencies.IDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange'); + +const db = new Dexie('test'); + +db.version(2).stores({ + history: 'id, created_at', + collections: 'id, created_at, &name', +}); + +db.version(1).stores({ + history: 'id, created_at', +}); + +// now we have db.history and db.collections + +// for setup and teardown tasks that are asynchronous, take care to RETURN the promise +describe('db test', () => { + beforeEach(() => db.history.clear().catch((err) => console.log(err))); + afterEach(() => db.history.clear().catch((err) => console.log(err))); + describe('history tests', () => { + it('can add history with id', async () => { + await db.history.put({ id: 8 }); + const count = await db.history.count(); + const arr = await db.history.toArray(); + expect(count).toEqual(1); + expect(arr[0].id).toEqual(8); + }); + + it('will not add history with an empty object', async () => { + db.history.put({}).catch((err) => expect(err.name).toEqual('DataError')); + const count = await db.history.count(); + expect(count).toEqual(0); + }); + + it('will not add history without an id', async () => { + await db.history + .put({ created_at: Date.now() }) + .catch((err) => expect(err.name).toEqual('DataError')); + const count = await db.history.count(); + expect(count).toEqual(0); + }); + }); +}); +// describe("collection tests", () => {}); +// }); diff --git a/__tests__/httpTest.js b/__tests__/httpTest.js index 9f038c5e6..57ec06cb0 100644 --- a/__tests__/httpTest.js +++ b/__tests__/httpTest.js @@ -1,113 +1,112 @@ -import ReqResCtrl from "../src/client/controllers/reqResController"; - - -xdescribe('REST API Requests', () => { - - beforeEach(() => { - state = { - currentTab: "First Tab", - reqResArray: [], - history: [], - collections: [], - warningMessage: {}, - newRequestFields: { - protocol: "", - restUrl: "http://", - wsUrl: "ws://", - gqlUrl: "https://", - grpcUrl: "", - url: "http://", - method: "GET", - graphQL: false, - gRPC: false, - network: "rest", - }, - newRequestHeaders: { - headersArr: [], - count: 0, - }, - newRequestStreams: { - streamsArr: [], - count: 0, - streamContent: [], - selectedPackage: null, - selectedRequest: null, - selectedService: null, - selectedServiceObj: null, - selectedStreamingType: null, - initialQuery: null, - queryArr: null, - protoPath: null, - services: null, - protoContent: "", - }, - newRequestCookies: { - cookiesArr: [], - count: 0, - }, - newRequestBody: { - bodyContent: "", - bodyVariables: "", - bodyType: "raw", - rawType: "text/plain", - JSONFormatted: true, - bodyIsNew: false, - }, - newRequestSSE: { - isSSE: false, - }, - introspectionData: { schemaSDL: null, clientSchema: null }, - dataPoints: [], - currentResponse: { - request: { - network: '' - } - }, - }; - }); - - describe('public API', () => { - it('it should GET information from a public API', () => { - // define request - const request = { - id: 'testID', - // created_at: 2020-11-04T19:33:55.829Z, - protocol: 'http://', - host: 'http://jsonplaceholder.typicode.com', - path: '/posts', - url: 'http://jsonplaceholder.typicode.com/posts', - graphQL: false, - gRPC: false, - timeSent: null, - timeReceived: null, - connection: 'uninitialized', - connectionType: null, - checkSelected: false, - protoPath: null, - request: { - method: 'GET', - headers: [ [Object] ], - cookies: [], - body: '', - bodyType: 'raw', - bodyVariables: '', - rawType: 'text/plain', - isSSE: false, - network: 'rest', - restUrl: 'http://jsonplaceholder.typicode.com/posts', - wsUrl: 'ws://', - gqlUrl: 'https://', - grpcUrl: '' - }, - response: { headers: null, events: null }, - checked: false, - minimized: false, - tab: 'First Tab' - } - - ReqResCtrl.openReqRes(request); - const response = state.reqResArray[0]; - expect(response.toEqual("hello")); - }); - }) -}) \ No newline at end of file +import ReqResCtrl from '../src/client/controllers/reqResController'; + +xdescribe('REST API Requests', () => { + let state; + beforeEach(() => { + state = { + currentTab: 'First Tab', + reqResArray: [], + history: [], + collections: [], + warningMessage: {}, + newRequestFields: { + protocol: '', + restUrl: 'http://', + wsUrl: 'ws://', + gqlUrl: 'https://', + grpcUrl: '', + url: 'http://', + method: 'GET', + graphQL: false, + gRPC: false, + network: 'rest', + }, + newRequestHeaders: { + headersArr: [], + count: 0, + }, + newRequestStreams: { + streamsArr: [], + count: 0, + streamContent: [], + selectedPackage: null, + selectedRequest: null, + selectedService: null, + selectedServiceObj: null, + selectedStreamingType: null, + initialQuery: null, + queryArr: null, + protoPath: null, + services: null, + protoContent: '', + }, + newRequestCookies: { + cookiesArr: [], + count: 0, + }, + newRequestBody: { + bodyContent: '', + bodyVariables: '', + bodyType: 'raw', + rawType: 'text/plain', + JSONFormatted: true, + bodyIsNew: false, + }, + newRequestSSE: { + isSSE: false, + }, + introspectionData: { schemaSDL: null, clientSchema: null }, + dataPoints: [], + currentResponse: { + request: { + network: '', + }, + }, + }; + }); + + describe('public API', () => { + it('it should GET information from a public API', () => { + // define request + const request = { + id: 'testID', + // created_at: 2020-11-04T19:33:55.829Z, + protocol: 'http://', + host: 'http://jsonplaceholder.typicode.com', + path: '/posts', + url: 'http://jsonplaceholder.typicode.com/posts', + graphQL: false, + gRPC: false, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + protoPath: null, + request: { + method: 'GET', + headers: [[Object]], + cookies: [], + body: '', + bodyType: 'raw', + bodyVariables: '', + rawType: 'text/plain', + isSSE: false, + network: 'rest', + restUrl: 'http://jsonplaceholder.typicode.com/posts', + wsUrl: 'ws://', + gqlUrl: 'https://', + grpcUrl: '', + }, + response: { headers: null, events: null }, + checked: false, + minimized: false, + tab: 'First Tab', + }; + + ReqResCtrl.openReqRes(request); + const response = state.reqResArray[0]; + expect(response.toEqual('hello')); + }); + }); +}); diff --git a/__tests__/protoParserTests.js b/__tests__/protoParserTests.js index 1f18084cd..8f04ad89e 100644 --- a/__tests__/protoParserTests.js +++ b/__tests__/protoParserTests.js @@ -1,99 +1,99 @@ -import protoParser from "../main_process/protoParser"; - -describe("testing protoParser", () => { - const protoFile = `syntax = 'proto3'; - - package helloworld; - - // The greeting service definition. - service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} - rpc SayHelloCS (stream HelloRequest) returns (HelloReply) {} - rpc SayHellos (HelloRequest) returns (stream HelloReply) {} - rpc SayHelloBidi (stream HelloRequest) returns (stream HelloReply) {} - } - - // The request message containing the user's name. - message HelloRequest { - string name = 1; - } - - // The response message containing the greetings - message HelloReply { - string message = 1; - } - - // The request message containing the user's name. - message HelloHowOldRequest { - int32 age = 1; - } - message HelloAge { - int32 age = 1; - }`; - describe("parser parses protos correctly", () => { - it("should get packageName", () => { - const parsedProto = protoParser(protoFile).then((data) => { - expect(data.packageName).toEqual("helloworld"); - }); - }); - it("should get serviceArray", () => { - const testArr = [ - { - messages: [{}, {}, {}, {}], - name: "Greeter", - packageName: "helloworld", - rpcs: [{}, {}, {}, {}], - }, - ]; - const parsedProto = protoParser(protoFile).then((data) => { - expect(data.serviceArr[0].messages).toHaveLength(4); - expect(data.serviceArr[0].rpcs).toHaveLength(4); - expect(data.serviceArr[0].name).toEqual("Greeter"); - expect(data.serviceArr[0].packageName).toEqual("helloworld"); - }); - }); - - it("should fill message content", () => { - const testArr = [ - { - messages: [ - { - name: "HelloRequest", - def: { - name: { type: "TYPE_STRING", nested: false, dependent: "" }, - }, - }, - { - name: "HelloRequest", - def: { - name: { type: "TYPE_STRING", nested: false, dependent: "" }, - }, - }, - { - name: "HelloRequest", - def: { - name: { type: "TYPE_STRING", nested: false, dependent: "" }, - }, - }, - { - name: "HelloRequest", - def: { - name: { type: "TYPE_STRING", nested: false, dependent: "" }, - }, - }, - ], - name: "Greeter", - packageName: "helloworld", - rpcs: [{}, {}, {}, {}], - }, - ]; - const parsedProto = protoParser(protoFile).then((data) => { - expect(data.serviceArr[0].messages[0]).toEqual(testArr[0].messages[0]); - expect(data.serviceArr[0].messages[0].def.name.type).toEqual( - "TYPE_STRING" - ); - }); - }); - }); -}); +import protoParser from '../main_process/protoParser'; + +describe('testing protoParser', () => { + const protoFile = `syntax = 'proto3'; + + package helloworld; + + // The greeting service definition. + service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayHelloCS (stream HelloRequest) returns (HelloReply) {} + rpc SayHellos (HelloRequest) returns (stream HelloReply) {} + rpc SayHelloBidi (stream HelloRequest) returns (stream HelloReply) {} + } + + // The request message containing the user's name. + message HelloRequest { + string name = 1; + } + + // The response message containing the greetings + message HelloReply { + string message = 1; + } + + // The request message containing the user's name. + message HelloHowOldRequest { + int32 age = 1; + } + message HelloAge { + int32 age = 1; + }`; + describe('parser parses protos correctly', () => { + it('should get packageName', () => { + const parsedProto = protoParser(protoFile).then((data) => { + expect(data.packageName).toEqual('helloworld'); + }); + }); + it('should get serviceArray', () => { + const testArr = [ + { + messages: [{}, {}, {}, {}], + name: 'Greeter', + packageName: 'helloworld', + rpcs: [{}, {}, {}, {}], + }, + ]; + const parsedProto = protoParser(protoFile).then((data) => { + expect(data.serviceArr[0].messages).toHaveLength(4); + expect(data.serviceArr[0].rpcs).toHaveLength(4); + expect(data.serviceArr[0].name).toEqual('Greeter'); + expect(data.serviceArr[0].packageName).toEqual('helloworld'); + }); + }); + + it('should fill message content', () => { + const testArr = [ + { + messages: [ + { + name: 'HelloRequest', + def: { + name: { type: 'TYPE_STRING', nested: false, dependent: '' }, + }, + }, + { + name: 'HelloRequest', + def: { + name: { type: 'TYPE_STRING', nested: false, dependent: '' }, + }, + }, + { + name: 'HelloRequest', + def: { + name: { type: 'TYPE_STRING', nested: false, dependent: '' }, + }, + }, + { + name: 'HelloRequest', + def: { + name: { type: 'TYPE_STRING', nested: false, dependent: '' }, + }, + }, + ], + name: 'Greeter', + packageName: 'helloworld', + rpcs: [{}, {}, {}, {}], + }, + ]; + const parsedProto = protoParser(protoFile).then((data) => { + expect(data.serviceArr[0].messages[0]).toEqual(testArr[0].messages[0]); + expect(data.serviceArr[0].messages[0].def.name.type).toEqual( + 'TYPE_STRING' + ); + }); + }); + }); +}); diff --git a/__tests__/uiReducer.js b/__tests__/uiReducer.js index 2cf31d148..8940fe2bf 100644 --- a/__tests__/uiReducer.js +++ b/__tests__/uiReducer.js @@ -1,33 +1,36 @@ -import uiReducer from "../src/client/reducers/ui"; -import { SET_COMPOSER_DISPLAY } from "../src/client/actions/actionTypes"; - -xdescribe("UI Reducer", () => { - let state; - - beforeEach(() => { - state = { - composerDisplay: "Request", - }; - }); - - describe("default state", () => { - it("should return a default state when given an undefined input", () => { - expect(uiReducer(undefined, { type: undefined })).toEqual(state); - }); - it("should return default state with unrecognized action types", () => { - expect(uiReducer(undefined, { type: "BAD_TYPE" })).toEqual(state); - }); - }); - - describe("should handle SET_COMPOSER_DISPLAY", () => { - it("should update the composerDisplay", () => { - const action = { - type: SET_COMPOSER_DISPLAY, - payload: "Warning", - }; - expect(uiReducer(undefined, action)).toEqual({ - composerDisplay: "Warning", - }); - }); - }); -}); +import uiReducer from '../src/client/reducers/ui'; +// import { SET_COMPOSER_DISPLAY } from '../src/client/actions/actionTypes'; + +describe('UI Reducer', () => { + let state; + + beforeEach(() => { + state = { + composerDisplay: 'Request', + sidebarActiveTab: 'composer', + workspaceActiveTab: 'workspace', + responsePaneActiveTab: 'events', + }; + }); + + describe('default state', () => { + it('should return a default state when given an undefined input', () => { + expect(uiReducer(undefined, { type: undefined })).toEqual(state); + }); + it('should return default state with unrecognized action types', () => { + expect(uiReducer(undefined, { type: 'BAD_TYPE' })).toEqual(state); + }); + }); + + describe('should handle SET_COMPOSER_DISPLAY', () => { + const action = { + type: 'SET_COMPOSER_DISPLAY', + payload: 'warning', + }; + + it('should update the composerDisplay', () => { + const { composerDisplay } = uiReducer(state, action); + expect(composerDisplay).toEqual(action.payload); + }); + }); +}); diff --git a/grpc_mockData/client.js b/grpc_mockData/client.js index 75ee944af..a839e8443 100644 --- a/grpc_mockData/client.js +++ b/grpc_mockData/client.js @@ -1,71 +1,71 @@ -const path = require("path"); -const protoLoader = require("@grpc/proto-loader"); -const grpc = require("@grpc/grpc-js"); - -// store proto path -const PROTO_PATH = path.resolve(__dirname, "./protos/hw2.proto"); - -// create package definition -const pd = protoLoader.loadSync(PROTO_PATH); -const loaded = grpc.loadPackageDefinition(pd); -// store package from proto file -const helloProto = loaded.helloworld; - -function main() { - // start client and create credentials - const client = new helloProto.Greeter( - "localhost:50051", - grpc.credentials.createInsecure() - ); - // set request value - // CLI prompt or hard code the variable for the message "name": "string" - let user; - // returns an array containing the command line arguments passed when the Node.js process was launched - // starts on 2 because process.argv contains the whole command-line invocation - // argv[0] and argv[1] will be node example.js - if (process.argv.length >= 3) { - user = process.argv[2]; - } else { - user = "world"; - } - - // create metadata - const meta = new grpc.Metadata(); - meta.add("testing", "metadata is working"); - - // unary - client.sayHello({ name: user }, meta, function (err, response) { - console.log("Greeting:", response.message); - }); - // server side streaming - const call = client.sayHellos({ name: user }, meta); - call.on("data", (data) => { - console.log("server streaming messages:", data); - }); - - // client side streaming - const stream = client.sayHelloCs(meta, (err, response) => { - if (err) { - console.log(err); - } else { - console.log(response); - } - client.close(); - }); - stream.write({ name: "hello 1st client side stream" }); - stream.write({ name: "hello 2nd client side stream" }); - // ends client streaming - stream.end({ name: "hello end client side stream" }); - - // bidi streaming - const streamBidi = client.sayHelloBidi(meta); - // reads streaming data - streamBidi.on("error", console.error); - streamBidi.on("data", console.log); - streamBidi.on("end", () => client.close()); - // write data - streamBidi.write({ name: "hello 1st bi-di stream" }); - // ends data - streamBidi.end({ name: "hello 2nd bi-di stream" }); -} -main(); +const path = require('path'); +const protoLoader = require('@grpc/proto-loader'); +const grpc = require('@grpc/grpc-js'); + +// store proto path +const PROTO_PATH = path.resolve(__dirname, './protos/hw2.proto'); + +// create package definition +const pd = protoLoader.loadSync(PROTO_PATH); +const loaded = grpc.loadPackageDefinition(pd); +// store package from proto file +const helloProto = loaded.helloworld; + +function main() { + // start client and create credentials + const client = new helloProto.Greeter( + 'localhost:50051', + grpc.credentials.createInsecure() + ); + // set request value + // CLI prompt or hard code the variable for the message "name": "string" + let user; + // returns an array containing the command line arguments passed when the Node.js process was launched + // starts on 2 because process.argv contains the whole command-line invocation + // argv[0] and argv[1] will be node example.js + if (process.argv.length >= 3) { + user = process.argv[2]; + } else { + user = 'world'; + } + + // create metadata + const meta = new grpc.Metadata(); + meta.add('testing', 'metadata is working'); + + // unary + client.sayHello({ name: user }, meta, (err, response) => { + console.log('Greeting:', response.message); + }); + // server side streaming + const call = client.sayHellos({ name: user }, meta); + call.on('data', (data) => { + console.log('server streaming messages:', data); + }); + + // client side streaming + const stream = client.sayHelloCs(meta, (err, response) => { + if (err) { + console.log(err); + } else { + console.log(response); + } + client.close(); + }); + stream.write({ name: 'hello 1st client side stream' }); + stream.write({ name: 'hello 2nd client side stream' }); + // ends client streaming + stream.end({ name: 'hello end client side stream' }); + + // bidi streaming + const streamBidi = client.sayHelloBidi(meta); + // reads streaming data + streamBidi.on('error', console.error); + streamBidi.on('data', console.log); + streamBidi.on('end', () => client.close()); + // write data + streamBidi.write({ name: 'hello 1st bi-di stream' }); + // ends data + streamBidi.end({ name: 'hello 2nd bi-di stream' }); +} +main(); diff --git a/grpc_mockData/server.js b/grpc_mockData/server.js index 85250b874..e5fcca8a2 100644 --- a/grpc_mockData/server.js +++ b/grpc_mockData/server.js @@ -1,123 +1,123 @@ -const path = require("path"); -const fs = require("fs"); -const hl = require("highland"); -const Mali = require("mali"); -// Mali needs the old grpc as a peer dependency so that should be installed as well -const grpc = require("@grpc/grpc-js"); - -// consider replacing highland with normal node code for converting array to streams - -const PROTO_PATH = path.join(__dirname, "./protos/hw2.proto"); -const HOSTPORT = "0.0.0.0:50051"; - -// Unary stream -// ctx = watch execution context -async function sayHello(ctx) { - // ctx contains both req and res objects - // sets key-value pair inside ctx.response.metadata as a replacement for headers - ctx.set("UNARY", "true"); - ctx.res = { message: "Hello " + ctx.req.name }; -} -// nested Unary stream -async function sayHelloNested(ctx) { - ctx.set("UNARY", "true"); - // nested unary response call - const firstPerson = ctx.req.firstPerson.name; - const secondPerson = ctx.req.secondPerson.name; - ctx.res = { - serverMessage: [ - { message: "Hello! " + firstPerson }, - { message: "Hello! " + secondPerson }, - ], - }; -} -// Server-Side Stream -// used highland library to manage asynchronous data -async function sayHellosSs(ctx) { - ctx.set("Server-side-stream", "true"); - // In case of UNARY and RESPONSE_STREAM calls it is simply the gRPC call's request - - const dataStream = [ - { - message: "You", - }, - { - message: "Are", - }, - { - message: "doing IT", - }, - { - message: "Champ", - }, - ]; - - const reqMessages = { message: "hello!!! " + ctx.req.name }; - // combine template with reqMessage - const updatedStream = [...dataStream, reqMessages]; - const makeStreamData = hl(updatedStream); - ctx.res = makeStreamData; - // ends server stream - ctx.res.end(); -} - -// Client-Side stream -async function sayHelloCs(ctx) { - // create new metadata - ctx.set("client-side-stream", "true"); - - const messages = []; - - return new Promise((resolve, reject) => { - // ctx.req is the incoming readable stream - hl(ctx.req) - .map((message) => { - console.log("parsed stream message with name key, ", message); - // currently the proto file is setup to only read streams with the key "name" - // other named keys will be pushed as an empty object - messages.push(message); - return undefined; - }) - .collect() - .toCallback((err, result) => { - if (err) return reject(err); - // console.log("messages ->", messages); - ctx.response.res = { message: `received ${messages.length} messages` }; - return resolve(); - }); - }); -} - -// Bi-Di stream -function sayHelloBidi(ctx) { - // create new metadata - ctx.set("bidi-stream", "true"); - console.log("got sayHelloBidi"); - // The execution context provides scripts and templates with access to the watch metadata - console.dir(ctx.metadata, { depth: 3, colors: true }); - let counter = 0; - ctx.req.on("data", (data) => { - counter++; - ctx.res.write({ message: "bidi stream: " + data.name }); - }); - - // calls end to client before closing server - ctx.req.on("end", () => { - // console.log(`done sayHelloBidi counter ${counter}`); - // ends server stream - ctx.res.end(); - }); -} - -/** - * Starts an RPC server that receives requests for the Greeter service at the - * sample server port - */ -function main() { - const app = new Mali(PROTO_PATH, "Greeter"); - app.use({ sayHello, sayHelloNested, sayHellosSs, sayHelloCs, sayHelloBidi }); - app.start(HOSTPORT); - console.log(`Greeter service running @ ${HOSTPORT}`); -} - -main(); +const path = require('path'); +const fs = require('fs'); +const hl = require('highland'); +const Mali = require('mali'); +// Mali needs the old grpc as a peer dependency so that should be installed as well +const grpc = require('@grpc/grpc-js'); + +// consider replacing highland with normal node code for converting array to streams + +const PROTO_PATH = path.join(__dirname, './protos/hw2.proto'); +const HOSTPORT = '0.0.0.0:50051'; + +// Unary stream +// ctx = watch execution context +async function sayHello(ctx) { + // ctx contains both req and res objects + // sets key-value pair inside ctx.response.metadata as a replacement for headers + ctx.set('UNARY', 'true'); + ctx.res = { message: `Hello ${ctx.req.name}` }; +} +// nested Unary stream +async function sayHelloNested(ctx) { + ctx.set('UNARY', 'true'); + // nested unary response call + const firstPerson = ctx.req.firstPerson.name; + const secondPerson = ctx.req.secondPerson.name; + ctx.res = { + serverMessage: [ + { message: `Hello! ${firstPerson}` }, + { message: `Hello! ${secondPerson}` }, + ], + }; +} +// Server-Side Stream +// used highland library to manage asynchronous data +async function sayHellosSs(ctx) { + ctx.set('Server-side-stream', 'true'); + // In case of UNARY and RESPONSE_STREAM calls it is simply the gRPC call's request + + const dataStream = [ + { + message: 'You', + }, + { + message: 'Are', + }, + { + message: 'doing IT', + }, + { + message: 'Champ', + }, + ]; + + const reqMessages = { message: `hello!!! ${ctx.req.name}` }; + // combine template with reqMessage + const updatedStream = [...dataStream, reqMessages]; + const makeStreamData = hl(updatedStream); + ctx.res = makeStreamData; + // ends server stream + ctx.res.end(); +} + +// Client-Side stream +async function sayHelloCs(ctx) { + // create new metadata + ctx.set('client-side-stream', 'true'); + + const messages = []; + + return new Promise((resolve, reject) => { + // ctx.req is the incoming readable stream + hl(ctx.req) + .map((message) => { + console.log('parsed stream message with name key, ', message); + // currently the proto file is setup to only read streams with the key "name" + // other named keys will be pushed as an empty object + messages.push(message); + return undefined; + }) + .collect() + .toCallback((err, result) => { + if (err) return reject(err); + // console.log("messages ->", messages); + ctx.response.res = { message: `received ${messages.length} messages` }; + return resolve(); + }); + }); +} + +// Bi-Di stream +function sayHelloBidi(ctx) { + // create new metadata + ctx.set('bidi-stream', 'true'); + console.log('got sayHelloBidi'); + // The execution context provides scripts and templates with access to the watch metadata + console.dir(ctx.metadata, { depth: 3, colors: true }); + let counter = 0; + ctx.req.on('data', (data) => { + counter++; + ctx.res.write({ message: `bidi stream: ${data.name}` }); + }); + + // calls end to client before closing server + ctx.req.on('end', () => { + // console.log(`done sayHelloBidi counter ${counter}`); + // ends server stream + ctx.res.end(); + }); +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + const app = new Mali(PROTO_PATH, 'Greeter'); + app.use({ sayHello, sayHelloNested, sayHellosSs, sayHelloCs, sayHelloBidi }); + app.start(HOSTPORT); + console.log(`Greeter service running @ ${HOSTPORT}`); +} + +main(); diff --git a/grpc_mockData/testServer.js b/grpc_mockData/testServer.js index 09a0bb2d8..7dd09db5a 100644 --- a/grpc_mockData/testServer.js +++ b/grpc_mockData/testServer.js @@ -1,25 +1,25 @@ -// an example of making a test server without Mali and pure @grpc - -const path = require("path"); -const grpc = require("@grpc/grpc-js"); -const protoLoader = require("@grpc/proto-loader"); - -// change PROTO_PATH to load a different mock proto file -const PROTO_PATH = path.resolve(__dirname, "protos/helloworld.proto"); -const PORT = "0.0.0.0:50051"; - -const proto = protoLoader.loadSync(PROTO_PATH); -const definition = grpc.loadPackageDefinition(proto); - -const greetMe = (call, callback) => { - callback(null, { reply: `Hey ${call.request.name}!` }); -}; - -const server = new grpc.Server(); - -server.addService(definition.HelloWorldService.service, { greetMe }); - -server.bindAsync(PORT, grpc.ServerCredentials.createInsecure(), (port) => { - server.start(); - console.log(`grpc server running on port ${PORT}`); -}); +// an example of making a test server without Mali and pure @grpc + +const path = require('path'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +// change PROTO_PATH to load a different mock proto file +const PROTO_PATH = path.resolve(__dirname, 'protos/helloworld.proto'); +const PORT = '0.0.0.0:50051'; + +const proto = protoLoader.loadSync(PROTO_PATH); +const definition = grpc.loadPackageDefinition(proto); + +const greetMe = (call, callback) => { + callback(null, { reply: `Hey ${call.request.name}!` }); +}; + +const server = new grpc.Server(); + +server.addService(definition.HelloWorldService.service, { greetMe }); + +server.bindAsync(PORT, grpc.ServerCredentials.createInsecure(), (port) => { + server.start(); + console.log(`grpc server running on port ${PORT}`); +}); diff --git a/jest.config.js b/jest.config.js index e540d2c4f..cb8869feb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,12 +1,12 @@ -module.exports = { - verbose: true, - runner: "@jest-runner/electron", - testEnvironment: "@jest-runner/electron/environment", - moduleNameMapper: { - // "collectCoverage": true, - electron: "/__mocks__/electronMock.js", - "\\.(css|less|sass|scss)$": "/__mocks__/styleMocks.js", - "\\.(gif|ttf|eot|svg|png)$": "/__mocks__/fileMock.js", - }, - resolver: null, -}; +module.exports = { + verbose: true, + runner: '@jest-runner/electron', + testEnvironment: '@jest-runner/electron/environment', + moduleNameMapper: { + // "collectCoverage": true, + electron: '/__mocks__/electronMock.js', + '\\.(css|less|sass|scss)$': '/__mocks__/styleMocks.js', + '\\.(gif|ttf|eot|svg|png)$': '/__mocks__/fileMock.js', + }, + resolver: null, +}; diff --git a/main.js b/main.js index 279c3c7cd..434ddb7c8 100644 --- a/main.js +++ b/main.js @@ -1,421 +1,457 @@ -// Allow self-signing HTTPS over TLS -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; -// Allow self-signing HTTPS over TLS -// Disabling Node's rejection of invalid/unauthorized certificates -// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; -// from stack overflow: https://stackoverflow.com/a/35633993/11606641 -// Your fix is insecure and shouldn't really be done at all, but is often done in development (it should never be done in production). -// The proper solution should be to put the self-signed certificate in your trusted root store OR to get a proper certificate signed by an existing Certificate Authority (which is already trusted by your server). - -// Import parts of electron to use -// app - Control your application's event lifecycle -// ipcMain - Communicate asynchronously from the main process to renderer processes - -// npm libraries -// debugger -const { app, BrowserWindow, ipcMain, dialog } = require("electron"); -//require("react-devtools-electron"); -const { - default: installExtension, - REACT_DEVELOPER_TOOLS, - REDUX_DEVTOOLS, -} = require("electron-devtools-installer"); -// Import Auto-Updater- Swell will update itself -const { autoUpdater } = require("electron-updater"); - -const path = require("path"); -const url = require("url"); -const fs = require("fs"); -const log = require("electron-log"); - -// proto-parser func for parsing .proto files -const protoParserFunc = require("./main_process/protoParser.js"); - -// require menu file -require("./menu/mainMenu"); -// require http controller file -require("./main_process/main_httpController.js")(); -// require gql controller file -require("./main_process/main_graphqlController")(); -// require grpc controller file -require("./main_process/main_grpcController.js")(); -// require ws controllerfile -require("./main_process/main_wsController.js")(); -// require mac touchBar -const { touchBar } = require("./main_process/main_touchbar.js"); - -// configure logging -autoUpdater.logger = log; -autoUpdater.logger.transports.file.level = "info"; -log.info("App starting..."); - -let mainWindow; - -/************************ - ******** SET isDev ***** - ************************/ -// default to production mode -let isDev = false; -// if running webpack-server, change to development mode -if (process.argv.includes("dev")) { - isDev = true; -} - -/************************* - ******* MODE DISPLAY **** - *************************/ - -isDev - ? console.log(` - -========================= - Launching in DEV mode -========================= - `) - : console.log(` - -================================ - Launching in PRODUCTION mode -================================ - `); - -if (process.platform === "win32") { - // if user is on windows... - app.commandLine.appendSwitch("high-dpi-support", "true"); - app.commandLine.appendSwitch("force-device-scale-factor", "1"); -} - -/*********************************************** - ******* createWindow function declaration ***** - ***********************************************/ - -function createWindow() { - // Create the new browser window instance. - mainWindow = new BrowserWindow({ - width: 2000, - height: 1000, - minWidth: 1304, - minHeight: 700, - backgroundColor: "-webkit-linear-gradient(top, #3dadc2 0%,#2f4858 100%)", - show: false, - title: "Swell", - // allowRunningInsecureContent: true, - webPreferences: { - nodeIntegration: false, - contextIsolation: process.env.NODE_ENV !== "test", //true if in dev mode - // enableRemoteModule: false, - sandbox: process.env.NODE_ENV !== "test", - webSecurity: true, - preload: path.resolve(__dirname, "preload.js"), - }, - icon: `${__dirname}/src/assets/icons/64x64.png`, - }); - - // and load the index.html of the app. - let indexPath; - - if (isDev) { - // if we are in dev mode load up 'http://localhost:8080/index.html' - indexPath = url.format({ - protocol: "http:", - host: "localhost:8080", - pathname: "index.html", - slashes: true, - }); - - // If we are in developer mode Add React & Redux DevTools to Electon App - installExtension(REACT_DEVELOPER_TOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log("An error occurred: ", err)); - - installExtension(REDUX_DEVTOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log("An error occurred: ", err)); - } else { - indexPath = url.format({ - // if we are not in dev mode load production build file - protocol: "file:", - pathname: path.join(__dirname, "dist", "index.html"), - slashes: true, - }); - } - - // our new app window will load content depending on the boolean value of the dev variable - mainWindow.loadURL(indexPath); - - // give our new window the earlier created touchbar - - mainWindow.setTouchBar(touchBar); - - // prevent webpack-dev-server from setting new title - mainWindow.on("page-title-updated", (e) => e.preventDefault()); - - // Don't show until we are ready and loaded - mainWindow.once("ready-to-show", () => { - mainWindow.show(); - // Open the DevTools automatically if developing - if (isDev && process.env.NODE_ENV !== "test") { - mainWindow.webContents.openDevTools(); - } - }); - - // Emitted when the window is closed. - mainWindow.on("closed", () => { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - - // tldr: Remove the BrowserWindow instance that we created earlier by setting its value to null when we exit Swell - mainWindow = null; - }); -} - -/********* end of createWindow declaration ******/ - -/**************************************** - ************** EVENT LISTENERS ********** - ****************************************/ - -// app.on('ready') will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. - -// if in prod mode, checkForUpdates after the window is created -app.on("ready", () => { - // createLoadingScreen(); - createWindow(); - if (!isDev) { - autoUpdater.checkForUpdates(); - } -}); - -// Quit when all windows are closed. -app.on("window-all-closed", () => { - app.quit(); -}); - -// Auto Updating Functionality -const sendStatusToWindow = (text) => { - log.info(text); - if (mainWindow) { - mainWindow.webContents.send("message", text); - } -}; - -ipcMain.on("check-for-update", () => { - //listens to ipcRenderer in UpdatePopUpContainer.jsx - console.log("check for update"); - if (!isDev) { - autoUpdater.checkForUpdates(); - } -}); -autoUpdater.on("checking-for-update", () => { - sendStatusToWindow("Checking for update..."); -}); -autoUpdater.on("update-available", (info) => { - sendStatusToWindow("Update available."); -}); -autoUpdater.on("update-not-available", (info) => { - sendStatusToWindow("Update not available."); -}); -autoUpdater.on("error", (err) => { - console.error("autoUpdater error -> ", err); - sendStatusToWindow(`Error in auto-updater`); -}); -autoUpdater.on("download-progress", (progressObj) => { - sendStatusToWindow( - `Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred} + '/' + ${progressObj.total} + )` - ); -}); -autoUpdater.on("update-downloaded", (info) => { - sendStatusToWindow("Update downloaded."); -}); - -autoUpdater.on("update-downloaded", (info) => { - // Wait 5 seconds, then quit and install - // In your application, you don't need to wait 500 ms. - // You could call autoUpdater.quitAndInstall(); immediately - autoUpdater.quitAndInstall(); -}); -ipcMain.on("quit-and-install", () => { - autoUpdater.quitAndInstall(); -}); -// App page reloads when user selects "Refresh" from pop-up dialog -ipcMain.on("fatalError", () => { - console.log("received fatal error"); - mainWindow.reload(); -}); -ipcMain.on("uncaughtException", () => { - console.log("received uncaught fatal error"); -}); - -app.on("activate", () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow(); - } -}); - -// export collection ipc now promise-based -ipcMain.on("export-collection", (event, args) => { - const content = JSON.stringify(args.collection); - dialog.showSaveDialog(null).then((resp) => { - if (resp.filePath === undefined) { - console.log("You didn't save the file"); - return; - } - - // fileName is a string that contains the path and filename created in the save file dialog. - fs.writeFile(resp.filePath, content, (err) => { - if (err) { - console.log("An error ocurred creating the file ", err.message); - } - }); - }); -}); - -ipcMain.on("import-collection", (event, args) => { - dialog.showOpenDialog(null).then((fileNames) => { - // reusable error message options object - const options = { - type: "error", - buttons: ["Okay"], - defaultId: 2, - title: "Error", - message: "", - detail: "", - }; - - // fileNames is an array that contains all the selected - if (fileNames === undefined) { - console.log("No file selected"); - return; - } - - // get first file path - not dynamic for multiple files - const filepath = fileNames.filePaths[0]; - - // get file extension - const ext = path.extname(filepath); - - // make sure if there is an extension that it is .txt - if (ext && ext !== ".txt") { - options.message = "Invalid File Type"; - options.detail = "Please use a .txt file"; - dialog.showMessageBox(null, options); - return; - } - - // names is the list of existing collection names in state - const collectionNames = args.map((obj) => obj.name); - fs.readFile(filepath, "utf-8", (err, data) => { - if (err) { - alert("An error ocurred reading the file :", err.message); - return; - } - // parse data, will throw error if not parsable - let parsed; - // parsed.name already exists - try { - parsed = JSON.parse(data); - } catch { - options.message = "Invalid File Structure"; - options.detail = "Please use a JSON object"; - dialog.showMessageBox(null, options); - return; - } - - if (parsed) { - // validate parsed data type and properties - if ( - typeof parsed !== "object" || - !parsed["id"] || - !parsed["name"] || - !parsed["reqResArray"] || - !parsed["created_at"] - ) { - options.message = "Invalid File"; - options.detail = "Please try again."; - dialog.showMessageBox(null, options); - return; - } - // duplicate collection exists already - if (collectionNames.includes(parsed.name)) { - options.message = "That collection already exists in the app"; - options.detail = "Please rename file to something else"; - dialog.showMessageBox(null, options); - return; - } - } - // send data to chromium for state update - event.sender.send("add-collection", JSON.stringify(JSON.parse(data))); - }); - }); -}); - -// ============ CONFIRM CLEAR HISTORY / RESPONSE COMMUNICATION =============== -ipcMain.on("confirm-clear-history", (event) => { - const opts = { - type: "warning", - buttons: ["Okay", "Cancel"], - message: "Are you sure you want to clear history?", - }; - - dialog - .showMessageBox(null, opts) - .then((response) => { - mainWindow.webContents.send("clear-history-response", response); - }) - .catch((err) => console.log(`Error on 'confirm-clear-history': ${err}`)); -}); - -// ================= GRPCProtoEntryForm Calls that uses protoParserFunc ======= - -// import-proto - -ipcMain.on("import-proto", (event) => { - let importedProto; - dialog - .showOpenDialog({ - buttonLabel: "Import Proto File", - properties: ["openFile", "multiSelections"], - filters: [{ name: "Protos", extensions: ["proto"] }], - }) - .then((filePaths) => { - if (!filePaths) return undefined; - // read uploaded proto file & save protoContent in the store - fs.readFile(filePaths.filePaths[0], "utf-8", (err, file) => { - // handle read error - if (err) { - return console.log("import-proto error reading file : ", err); - } - importedProto = file; - protoParserFunc(importedProto).then((protoObj) => { - // console.log( - // "finished with logic. about to send importedProto : ", - // importedProto, - // " and protoObj : ", - // protoObj - // ); - mainWindow.webContents.send("proto-info", importedProto, protoObj); - }); - }); - }) - .catch((err) => { - console.log("error in import-proto", err); - }); -}); - -// protoParserFunc-request. Just runs the function and returns the value back to GRPCProtoEntryForm - -ipcMain.on("protoParserFunc-request", (event, data) => { - protoParserFunc(data) - .then((result) => { - mainWindow.webContents.send("protoParserFunc-return", result); - }) - .catch((err) => { - console.log("error in protoParserFunc-request:, ", err); - mainWindow.webContents.send("protoParserFunc-return", { error: err }); - }); -}); +// Allow self-signing HTTPS over TLS +process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; +// Allow self-signing HTTPS over TLS +// Disabling Node's rejection of invalid/unauthorized certificates +// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; +// from stack overflow: https://stackoverflow.com/a/35633993/11606641 +// Your fix is insecure and shouldn't really be done at all, but is often done in development (it should never be done in production). +// The proper solution should be to put the self-signed certificate in your trusted root store OR to get a proper certificate signed by an existing Certificate Authority (which is already trusted by your server). + +// Import parts of electron to use +// app - Control your application's event lifecycle +// ipcMain - Communicate asynchronously from the main process to renderer processes + +const { app, BrowserWindow, ipcMain, dialog } = require('electron'); + +const { autoUpdater } = require('electron-updater'); +const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + REDUX_DEVTOOLS, +} = require('electron-devtools-installer'); + +const path = require('path'); +const url = require('url'); +const fs = require('fs'); +const log = require('electron-log'); + +// proto-parser func for parsing .proto files +const protoParserFunc = require('./main_process/protoParser.js'); + +// openapi parser func for parsing openAPI documents in JSON or YAML format +const openapiParserFunc = require('./main_process/openapiParser.js'); + +// require menu file +require('./menu/mainMenu'); +// require http controller file +require('./main_process/main_httpController.js')(); +// require gql controller file +require('./main_process/main_graphqlController')(); +// require grpc controller file +require('./main_process/main_grpcController.js')(); +// require ws controller file +require('./main_process/main_wsController.js')(); +// require mac touchBar +const { touchBar } = require('./main_process/main_touchbar.js'); + +// configure logging +autoUpdater.logger = log; +autoUpdater.logger.transports.file.level = 'info'; +log.info('App starting...'); + +let mainWindow; + +/** ********************** + ******** SET isDev ***** + *********************** */ +// default to production mode +let isDev = false; +// if running webpack-server, change to development mode +if (process.argv.includes('dev')) { + isDev = true; +} + +/** *********************** + ******* MODE DISPLAY **** + ************************ */ + +isDev + ? console.log(` + +========================= + Launching in DEV mode +========================= + `) + : console.log(` + +================================ + Launching in PRODUCTION mode +================================ + `); + +if (process.platform === 'win32') { + // if user is on windows... + app.commandLine.appendSwitch('high-dpi-support', 'true'); + // app.commandLine.appendSwitch("force-device-scale-factor", "1"); +} + +/** ********************************************* + ******* createWindow function declaration ***** + ********************************************** */ + +function createWindow() { + // Create the new browser window instance. + mainWindow = new BrowserWindow({ + width: 2000, + height: 1000, + minWidth: 1304, + minHeight: 700, + backgroundColor: '-webkit-linear-gradient(top, #3dadc2 0%,#2f4858 100%)', + show: false, + title: 'Swell', + webPreferences: { + nodeIntegration: false, + contextIsolation: process.env.NODE_ENV !== 'test', // true if in dev mode + sandbox: process.env.NODE_ENV !== 'test', + webSecurity: true, + preload: path.resolve(__dirname, 'preload.js'), + }, + icon: `${__dirname}/src/assets/icons/64x64.png`, + }); + + // and load the index.html of the app. + let indexPath; + + if (isDev) { + // if we are in dev mode load up 'http://localhost:8080/index.html' + indexPath = url.format({ + protocol: 'http:', + host: 'localhost:8080', + pathname: 'index.html', + slashes: true, + }); + + // If we are in developer mode Add React & Redux DevTools to Electron App + installExtension(REACT_DEVELOPER_TOOLS) + .then((name) => console.log(`Added Extension: ${name}`)) + .catch((err) => console.log('An error occurred: ', err)); + + installExtension(REDUX_DEVTOOLS) + .then((name) => console.log(`Added Extension: ${name}`)) + .catch((err) => console.log('An error occurred: ', err)); + } else { + indexPath = url.format({ + // if we are not in dev mode load production build file + protocol: 'file:', + pathname: path.join(__dirname, 'dist', 'index.html'), + slashes: true, + }); + } + + // our new app window will load content depending on the boolean value of the dev variable + mainWindow.loadURL(indexPath); + + // give our new window the earlier created touchbar + mainWindow.setTouchBar(touchBar); + + // prevent webpack-dev-server from setting new title + mainWindow.on('page-title-updated', (e) => e.preventDefault()); + + // Don't show until we are ready and loaded + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + // Open the DevTools automatically if developing + if (isDev && process.env.NODE_ENV !== 'test') { + mainWindow.webContents.openDevTools(); + } + }); + + // Emitted when the window is closed. + mainWindow.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + + // tldr: Remove the BrowserWindow instance that we created earlier by setting its value to null when we exit Swell + mainWindow = null; + }); +} + +/** ******* end of createWindow declaration ***** */ + +/** ************************************** + ************** EVENT LISTENERS ********** + *************************************** */ + +// app.on('ready') will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. + +// if in prod mode, checkForUpdates after the window is created +app.on('ready', () => { + createWindow(); + if (!isDev) { + autoUpdater.checkForUpdates(); + } +}); + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + app.quit(); +}); + +// Auto Updating Functionality +const sendStatusToWindow = (text) => { + log.info(text); + if (mainWindow) { + mainWindow.webContents.send('message', text); + } +}; + +ipcMain.on('check-for-update', () => { + // listens to ipcRenderer in UpdatePopUpContainer.jsx + console.log('check for update'); + if (!isDev) { + autoUpdater.checkForUpdates(); + } +}); +autoUpdater.on('checking-for-update', () => { + sendStatusToWindow('Checking for update...'); +}); +autoUpdater.on('update-available', (info) => { + sendStatusToWindow('Update available.'); +}); +autoUpdater.on('update-not-available', (info) => { + sendStatusToWindow('Update not available.'); +}); +autoUpdater.on('error', (err) => { + console.error('autoUpdater error -> ', err); + sendStatusToWindow(`Error in auto-updater`); +}); +autoUpdater.on('download-progress', (progressObj) => { + sendStatusToWindow( + `Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred} + '/' + ${progressObj.total} + )` + ); +}); +autoUpdater.on('update-downloaded', (info) => { + sendStatusToWindow('Update downloaded.'); +}); + +autoUpdater.on('update-downloaded', (info) => { + // Wait 5 seconds, then quit and install + // In your application, you don't need to wait 500 ms. + // You could call autoUpdater.quitAndInstall(); immediately + autoUpdater.quitAndInstall(); +}); +ipcMain.on('quit-and-install', () => { + autoUpdater.quitAndInstall(); +}); +// App page reloads when user selects "Refresh" from pop-up dialog +ipcMain.on('fatalError', () => { + console.log('received fatal error'); + mainWindow.reload(); +}); +ipcMain.on('uncaughtException', () => { + console.log('received uncaught fatal error'); +}); + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow(); + } +}); + +// export collection ipc now promise-based +ipcMain.on('export-collection', (event, args) => { + const content = JSON.stringify(args.collection); + dialog.showSaveDialog(null).then((resp) => { + if (resp.filePath === undefined) { + console.log("You didn't save the file"); + return; + } + + // fileName is a string that contains the path and filename created in the save file dialog. + fs.writeFile(resp.filePath, content, (err) => { + if (err) { + console.log('An error ocurred creating the file ', err.message); + } + }); + }); +}); + +ipcMain.on('import-collection', (event, args) => { + dialog.showOpenDialog(null).then((fileNames) => { + // reusable error message options object + const options = { + type: 'error', + buttons: ['Okay'], + defaultId: 2, + title: 'Error', + message: '', + detail: '', + }; + + // fileNames is an array that contains all the selected + if (fileNames === undefined) { + console.log('No file selected'); + return; + } + + // get first file path - not dynamic for multiple files + const filepath = fileNames.filePaths[0]; + + // get file extension + const ext = path.extname(filepath); + + // make sure if there is an extension that it is .txt + if (ext && ext !== '.txt') { + options.message = 'Invalid File Type'; + options.detail = 'Please use a .txt file'; + dialog.showMessageBox(null, options); + return; + } + + // names is the list of existing collection names in state + const collectionNames = args.map((obj) => obj.name); + fs.readFile(filepath, 'utf-8', (err, data) => { + if (err) { + alert('An error ocurred reading the file :', err.message); + return; + } + // parse data, will throw error if not parsable + let parsed; + // parsed.name already exists + try { + parsed = JSON.parse(data); + } catch { + options.message = 'Invalid File Structure'; + options.detail = 'Please use a JSON object'; + dialog.showMessageBox(null, options); + return; + } + + if (parsed) { + // validate parsed data type and properties + if ( + typeof parsed !== 'object' || + !parsed.id || + !parsed.name || + !parsed.reqResArray || + !parsed.created_at + ) { + options.message = 'Invalid File'; + options.detail = 'Please try again.'; + dialog.showMessageBox(null, options); + return; + } + // duplicate collection exists already + if (collectionNames.includes(parsed.name)) { + options.message = 'That collection already exists in the app'; + options.detail = 'Please rename file to something else'; + dialog.showMessageBox(null, options); + return; + } + } + // send data to chromium for state update + event.sender.send('add-collection', JSON.stringify(JSON.parse(data))); + }); + }); +}); + +// ============ CONFIRM CLEAR HISTORY / RESPONSE COMMUNICATION =============== +ipcMain.on('confirm-clear-history', (event) => { + const opts = { + type: 'warning', + buttons: ['Okay', 'Cancel'], + message: 'Are you sure you want to clear history?', + }; + + dialog + .showMessageBox(null, opts) + .then((response) => { + mainWindow.webContents.send('clear-history-response', response); + }) + .catch((err) => console.log(`Error on 'confirm-clear-history': ${err}`)); +}); + +// ================= GRPCProtoEntryForm Calls that uses protoParserFunc ======= +ipcMain.on('import-proto', (event) => { + let importedProto; + dialog + .showOpenDialog({ + buttonLabel: 'Import Proto File', + properties: ['openFile', 'multiSelections'], + filters: [{ name: 'Protos', extensions: ['proto'] }], + }) + .then((filePaths) => { + if (!filePaths) return undefined; + // read uploaded proto file & save protoContent in the store + fs.readFile(filePaths.filePaths[0], 'utf-8', (err, file) => { + // handle read error + if (err) { + return console.log('import-proto error reading file : ', err); + } + importedProto = file; + protoParserFunc(importedProto).then((protoObj) => { + // console.log( + // "finished with logic. about to send importedProto : ", + // importedProto, + // " and protoObj : ", + // protoObj + // ); + mainWindow.webContents.send('proto-info', importedProto, protoObj); + }); + }); + }) + .catch((err) => { + console.log('error in import-proto', err); + }); +}); + +// protoParserFunc-request +// Runs the function and returns the value back to GRPCProtoEntryForm +ipcMain.on('protoParserFunc-request', (event, data) => { + protoParserFunc(data) + .then((result) => { + mainWindow.webContents.send('protoParserFunc-return', result); + }) + .catch((err) => { + console.log('error in protoParserFunc-request:, ', err); + mainWindow.webContents.send('protoParserFunc-return', { error: err }); + }); +}); + +// ====== Loading and parsing an OpenAPI Document with openapiParserFunc ====== +ipcMain.on('import-openapi', (event) => { + let importedDocument; + dialog + .showOpenDialog({ + buttonLabel: 'Import OpenApi File', + properties: ['openFile', 'multiSelections'], + }) + .then((filePaths) => { + if (!filePaths) return undefined; + // read uploaded document & save in the redux store + fs.readFile(filePaths.filePaths[0], 'utf-8', (err, file) => { + // handle read error + if (err) { + return console.log('import-openapi error reading file : ', err); + } + importedDocument = file; + const documentObj = openapiParserFunc(importedDocument); + mainWindow.webContents.send( + 'openapi-info', + importedDocument, + documentObj + ); + }); + }) + .catch((err) => { + console.log('error in import-openapi', err); + }); +}); + +// openapiParserFunc-request. +// Runs the function and returns the value back to OpenAPIDocumentEntryForm +ipcMain.on('openapiParserFunc-request', (event, data) => { + openapiParserFunc(data) + .then((result) => { + mainWindow.webContents.send('openapiParserFunc-return', result); + }) + .catch((err) => { + console.log('error in openapiParserFunc-request:, ', err); + mainWindow.webContents.send('openapiParserFunc-return', { error: err }); + }); +}); diff --git a/main_process/SSEController.js b/main_process/SSEController.js index b25ea9cbf..456e96ea8 100644 --- a/main_process/SSEController.js +++ b/main_process/SSEController.js @@ -1,81 +1,86 @@ -const http = require('http'); -// native browser api that we are bringing in to work in a node environmnet -const EventSource = require('eventsource'); - -const SSEController = {}; - -// keep reference to what will be our EventSource that listens for SSE's -SSEController.sseOpenConnections = {}; - -SSEController.returnErrorToFrontEnd = (reqResObj, event, error) => { - reqResObj.connection = "error"; - reqResObj.error = error; - reqResObj.response.events.push(error.message); - event.sender.send("reqResUpdate", reqResObj); -} - -SSEController.createStream = (reqResObj, options, event) => { - // got options from httpController - const { headers } = options; - - /* because EventSource cannot access headers, we are making a regular get request to SSE server to get its headers, - and then passing those headers into function where we will be connecting our EventSource, there will a time delay - between the time the user opens the request and the server sends back its first response. We keep reference to the time - the first request was made to account for that time difference later on. */ - const startTime = Date.now(); - - try { - http.get(headers.url, (res) => { - reqResObj.response.headers = {...res.headers}; - reqResObj.connection = 'open'; - reqResObj.connectionType = 'SSE'; - // this is for purpose of logic in graph.jsx, which needs the entire req/res obj to have a timeReceived - reqResObj.timeReceived = Date.now(); - // invoke function that will create an EventSource - SSEController.readStream(reqResObj, event, Date.now() - startTime); - res.destroy(); - }).on('error', (e) => { - console.error(`Got error: ${e.message}`) - }); - } catch (error) { - console.log('error making initial get reuest in SSE controller\n', error.message); - SSEController.returnErrorToFrontEnd(reqResObj, event, error); - } -}; - -SSEController.readStream = (reqResObj, event, timeDiff) => { - // EventSource listens for SSE's and process specially formatted data from them, as well as adding other useful information - const sse = new EventSource(reqResObj.url); - SSEController.sseOpenConnections[reqResObj.id] = sse; - - sse.onopen = () => { - // console.log(`SSE at ${reqResObj.url} opened!`); - } - - sse.onmessage = (message) => { - // message is not a javascript object, so we spread its contents into one - const newMessage = { ...message }; - // this is where where account for any time lost between the first AJAX request and the creation of the EventSource - newMessage.timeReceived = Date.now() - timeDiff; - - reqResObj.response.events.push(newMessage); - return event.sender.send('reqResUpdate', reqResObj); - }; - sse.onerror = (error) => { - console.log('there was an error in SSEController.readStream', error); - sse.close(); - SSEController.returnErrorToFrontEnd(reqResObj, event, error); - }; -}; - -SSEController.closeConnection = (reqResId) => { - if (!SSEController.sseOpenConnections[reqResId]) { - return; - } - - const sse = SSEController.sseOpenConnections[reqResId]; - sse.close(); - delete SSEController.sseOpenConnections[reqResId]; -} - -module.exports = SSEController; \ No newline at end of file +const http = require('http'); +// native browser api that we are bringing in to work in a node environment +const EventSource = require('eventsource'); + +const SSEController = {}; + +// keep reference to what will be our EventSource that listens for SSE's +SSEController.sseOpenConnections = {}; + +SSEController.returnErrorToFrontEnd = (reqResObj, event, error) => { + reqResObj.connection = 'error'; + reqResObj.error = error; + reqResObj.response.events.push(error.message); + event.sender.send('reqResUpdate', reqResObj); +}; + +SSEController.createStream = (reqResObj, options, event) => { + // got options from httpController + const { headers } = options; + + /* because EventSource cannot access headers, we are making a regular get request to SSE server to get its headers, + and then passing those headers into function where we will be connecting our EventSource, there will a time delay + between the time the user opens the request and the server sends back its first response. We keep reference to the time + the first request was made to account for that time difference later on. */ + const startTime = Date.now(); + + try { + http + .get(headers.url, (res) => { + reqResObj.response.headers = { ...res.headers }; + reqResObj.connection = 'open'; + reqResObj.connectionType = 'SSE'; + // this is for purpose of logic in graph.jsx, which needs the entire req/res obj to have a timeReceived + reqResObj.timeReceived = Date.now(); + // invoke function that will create an EventSource + SSEController.readStream(reqResObj, event, Date.now() - startTime); + res.destroy(); + }) + .on('error', (e) => { + console.error(`Got error: ${e.message}`); + }); + } catch (error) { + console.log( + 'error making initial get request in SSE controller\n', + error.message + ); + SSEController.returnErrorToFrontEnd(reqResObj, event, error); + } +}; + +SSEController.readStream = (reqResObj, event, timeDiff) => { + // EventSource listens for SSE's and process specially formatted data from them, as well as adding other useful information + const sse = new EventSource(reqResObj.url); + SSEController.sseOpenConnections[reqResObj.id] = sse; + + sse.onopen = () => { + // console.log(`SSE at ${reqResObj.url} opened!`); + }; + + sse.onmessage = (message) => { + // message is not a javascript object, so we spread its contents into one + const newMessage = { ...message }; + // this is where where account for any time lost between the first AJAX request and the creation of the EventSource + newMessage.timeReceived = Date.now() - timeDiff; + + reqResObj.response.events.push(newMessage); + return event.sender.send('reqResUpdate', reqResObj); + }; + sse.onerror = (error) => { + console.log('there was an error in SSEController.readStream', error); + sse.close(); + SSEController.returnErrorToFrontEnd(reqResObj, event, error); + }; +}; + +SSEController.closeConnection = (reqResId) => { + if (!SSEController.sseOpenConnections[reqResId]) { + return; + } + + const sse = SSEController.sseOpenConnections[reqResId]; + sse.close(); + delete SSEController.sseOpenConnections[reqResId]; +}; + +module.exports = SSEController; diff --git a/main_process/main_graphqlController.js b/main_process/main_graphqlController.js index b8acc3359..3e6be01a3 100644 --- a/main_process/main_graphqlController.js +++ b/main_process/main_graphqlController.js @@ -1,217 +1,214 @@ -const { ApolloClient } = require("apollo-client"); -const gql = require("graphql-tag"); -const { InMemoryCache } = require("apollo-cache-inmemory"); -const { createHttpLink } = require("apollo-link-http"); -const { ApolloLink } = require("apollo-link"); -const { introspectionQuery } = require("graphql"); -const { onError } = require("apollo-link-error"); -const fetch2 = require("node-fetch"); -const cookie = require("cookie"); -const { ipcMain } = require("electron"); - -const testingController = require("./main_testingController"); - -const graphqlController = { - /* NEED TO INCORPORATE COOKIES AND HEADERS IN QUERIES AND MUTATIONS */ - openConnection(event, args) { - { - const reqResObj = args.reqResObj; - - // populating headers object with response headers - except for Content-Type - const headers = {}; - reqResObj.request.headers - .filter((item) => item.key !== "Content-Type") - .forEach((item) => { - headers[item.key] = item.value; - }); - - // request cookies from reqResObj to request headers - let cookies = ""; - if (reqResObj.request.cookies.length) { - cookies = reqResObj.request.cookies.reduce((acc, userCookie) => { - return acc + `${userCookie.key}=${userCookie.value}; `; - }, ""); - } - headers.Cookie = cookies; - - // afterware takes headers from context response object, copies to reqResObj - const afterLink = new ApolloLink((operation, forward) => { - return forward(operation).map((response) => { - const context = operation.getContext(); - const headers = context.response.headers.entries(); - for (const headerItem of headers) { - const key = headerItem[0] - .split("-") - .map((item) => item[0].toUpperCase() + item.slice(1)) - .join("-"); - reqResObj.response.headers[key] = headerItem[1]; - - // if cookies were sent by server, parse first key-value pair, then cookie.parse the rest - if (headerItem[0] === "set-cookie") { - const parsedCookies = []; - const cookieStrArr = headerItem[1].split(", "); - cookieStrArr.forEach((thisCookie) => { - thisCookie = thisCookie.toLowerCase(); - // index of first semicolon - const idx = thisCookie.search(/[;]/g); - // first key value pair - const keyValueArr = thisCookie.slice(0, idx).split("="); - // cookie contents after first key value pair - const parsedRemainingCookieProperties = cookie.parse( - thisCookie.slice(idx + 1) - ); - - const parsedCookie = { - ...parsedRemainingCookieProperties, - name: keyValueArr[0], - value: keyValueArr[1], - }; - - parsedCookies.push(parsedCookie); - }); - reqResObj.response.cookies = parsedCookies; - } - } - - return response; - }); - }); - - // creates http connection to host - const httpLink = createHttpLink({ - uri: reqResObj.url, - headers, - credentials: "include", - fetch: fetch2, - }); - - const errorLink = onError(({ graphQLErrors, networkError }) => { - if (networkError) { - reqResObj.error = JSON.stringify(networkError); - event.sender.send("reply-gql", { error: networkError, reqResObj }); - } - try { - // check if there are any errors in the graphQLErrors array - if (graphQLErrors.length !== 0) { - graphQLErrors.forEach((currError) => { - reqResObj.error = JSON.stringify(currError); - event.sender.send("reply-gql", { error: currError, reqResObj }); - }); - } - } catch (err) { - console.log("Error in errorLink:", err); - } - }); - - // additive composition of multiple links - // https://www.apollographql.com/docs/react/api/link/introduction/#composing-a-link-chain - const link = ApolloLink.from([afterLink, errorLink, httpLink]); - - const client = new ApolloClient({ - link, - cache: new InMemoryCache(), - }); - - try { - const body = gql` - ${reqResObj.request.body} - `; - // graphql variables: https://graphql.org/learn/queries/#variables - const variables = reqResObj.request.bodyVariables - ? JSON.parse(reqResObj.request.bodyVariables) - : {}; - - if (reqResObj.request.method === "QUERY") { - client - .query({ query: body, variables, context: headers }) - .then((data) => { - //handle tests - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - data, - true - ); - } - event.sender.send("reply-gql", { reqResObj, data }); - }) - .catch((err) => { - // error is actually sent to graphQLController via "errorLink" - console.log("gql query error in main_graphqlController.js", err); - }); - } else if (reqResObj.request.method === "MUTATION") { - client - .mutate({ mutation: body, variables, context: headers }) - .then((data) => { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - data - ); - event.sender.send("reply-gql", { reqResObj, data }); - }) - .catch((err) => { - // error is actually sent to graphQLController via "errorLink" - console.error( - "gql mutation error in main_graphqlController.js", - err - ); - }); - } - } catch (err) { - console.log( - "error trying gql query/mutation in main_graphqlController.js", - err - ); - } - } - }, - - /* NEED TO INCORPORATE COOKIES AND HEADERS IN INTROSPECTION */ - introspect(event, introspectionObject) { - const req = JSON.parse(introspectionObject); - - // Reformat headers - const headers = {}; - req.headers.forEach(({ active, key, value }) => { - if (active) headers[key] = value; - }); - // Reformat cookies - let cookies = ""; - if (req.cookies.length) { - cookies = req.cookies.reduce((acc, userCookie) => { - if (userCookie.active) - return acc + `${userCookie.key}=${userCookie.value}; `; - return acc; - }, ""); - } - headers.Cookie = cookies; - - fetch2(req.url, { - method: "POST", - headers, - body: JSON.stringify({ query: introspectionQuery }), - }) - .then((resp) => resp.json()) - .then((data) => { - return event.sender.send("introspect-reply", data.data); - }) - .catch((err) => - event.sender.send( - "introspect-reply", - "Error: Please enter a valid GraphQL API URI" - ) - ); - }, -}; - -module.exports = () => { - ipcMain.on("open-gql", (event, args) => { - graphqlController.openConnection(event, args); - }); - - ipcMain.on("introspect", (event, introspectionObject) => { - graphqlController.introspect(event, introspectionObject); - }); -}; +const { ApolloClient } = require('apollo-client'); +const gql = require('graphql-tag'); +const { InMemoryCache } = require('apollo-cache-inmemory'); +const { createHttpLink } = require('apollo-link-http'); +const { ApolloLink } = require('apollo-link'); +const { introspectionQuery } = require('graphql'); +const { onError } = require('apollo-link-error'); +const fetch2 = require('node-fetch'); +const cookie = require('cookie'); +const { ipcMain } = require('electron'); + +const testingController = require('./main_testingController'); + +const graphqlController = { + /* NEED TO INCORPORATE COOKIES AND HEADERS IN QUERIES AND MUTATIONS */ + openConnection(event, args) { + const { reqResObj } = args; + + // populating headers object with response headers - except for Content-Type + const headers = {}; + reqResObj.request.headers + .filter((item) => item.key !== 'Content-Type') + .forEach((item) => { + headers[item.key] = item.value; + }); + + // request cookies from reqResObj to request headers + let cookies = ''; + if (reqResObj.request.cookies.length) { + cookies = reqResObj.request.cookies.reduce( + (acc, userCookie) => `${acc}${userCookie.key}=${userCookie.value}; `, + '' + ); + } + headers.Cookie = cookies; + + // afterware takes headers from context response object, copies to reqResObj + const afterLink = new ApolloLink((operation, forward) => + forward(operation).map((response) => { + const context = operation.getContext(); + const headers = context.response.headers.entries(); + for (const headerItem of headers) { + const key = headerItem[0] + .split('-') + .map((item) => item[0].toUpperCase() + item.slice(1)) + .join('-'); + reqResObj.response.headers[key] = headerItem[1]; + + // if cookies were sent by server, parse first key-value pair, then cookie.parse the rest + if (headerItem[0] === 'set-cookie') { + const parsedCookies = []; + const cookieStrArr = headerItem[1].split(', '); + cookieStrArr.forEach((thisCookie) => { + thisCookie = thisCookie.toLowerCase(); + // index of first semicolon + const idx = thisCookie.search(/[;]/g); + // first key value pair + const keyValueArr = thisCookie.slice(0, idx).split('='); + // cookie contents after first key value pair + const parsedRemainingCookieProperties = cookie.parse( + thisCookie.slice(idx + 1) + ); + + const parsedCookie = { + ...parsedRemainingCookieProperties, + name: keyValueArr[0], + value: keyValueArr[1], + }; + + parsedCookies.push(parsedCookie); + }); + reqResObj.response.cookies = parsedCookies; + } + } + + return response; + }) + ); + + // creates http connection to host + const httpLink = createHttpLink({ + uri: reqResObj.url, + headers, + credentials: 'include', + fetch: fetch2, + }); + + const errorLink = onError(({ graphQLErrors, networkError }) => { + if (networkError) { + reqResObj.error = JSON.stringify(networkError); + event.sender.send('reply-gql', { error: networkError, reqResObj }); + } + try { + // check if there are any errors in the graphQLErrors array + if (graphQLErrors.length !== 0) { + graphQLErrors.forEach((currError) => { + reqResObj.error = JSON.stringify(currError); + event.sender.send('reply-gql', { error: currError, reqResObj }); + }); + } + } catch (err) { + console.log('Error in errorLink:', err); + } + }); + + // additive composition of multiple links + // https://www.apollographql.com/docs/react/api/link/introduction/#composing-a-link-chain + const link = ApolloLink.from([afterLink, errorLink, httpLink]); + + const client = new ApolloClient({ + link, + cache: new InMemoryCache(), + }); + + try { + const body = gql` + ${reqResObj.request.body} + `; + // graphql variables: https://graphql.org/learn/queries/#variables + const variables = reqResObj.request.bodyVariables + ? JSON.parse(reqResObj.request.bodyVariables) + : {}; + + if (reqResObj.request.method === 'QUERY') { + client + .query({ query: body, variables, context: headers }) + .then((data) => { + // handle tests + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + data, + true + ); + } + event.sender.send('reply-gql', { reqResObj, data }); + }) + .catch((err) => { + // error is actually sent to graphQLController via "errorLink" + console.log('gql query error in main_graphqlController.js', err); + }); + } else if (reqResObj.request.method === 'MUTATION') { + client + .mutate({ mutation: body, variables, context: headers }) + .then((data) => { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + data + ); + event.sender.send('reply-gql', { reqResObj, data }); + }) + .catch((err) => { + // error is actually sent to graphQLController via "errorLink" + console.error( + 'gql mutation error in main_graphqlController.js', + err + ); + }); + } + } catch (err) { + console.log( + 'error trying gql query/mutation in main_graphqlController.js', + err + ); + } + }, + + /* NEED TO INCORPORATE COOKIES AND HEADERS IN INTROSPECTION */ + introspect(event, introspectionObject) { + const req = JSON.parse(introspectionObject); + + // Reformat headers + const headers = {}; + req.headers.forEach(({ active, key, value }) => { + if (active) headers[key] = value; + }); + // Reformat cookies + let cookies = ''; + if (req.cookies.length) { + cookies = req.cookies.reduce((acc, userCookie) => { + if (userCookie.active) + return `${acc}${userCookie.key}=${userCookie.value}; `; + return acc; + }, ''); + } + headers.Cookie = cookies; + + fetch2(req.url, { + method: 'POST', + headers, + body: JSON.stringify({ query: introspectionQuery }), + }) + .then((resp) => resp.json()) + .then((data) => event.sender.send('introspect-reply', data.data)) + .catch((err) => + event.sender.send( + 'introspect-reply', + 'Error: Please enter a valid GraphQL API URI' + ) + ); + }, +}; + +module.exports = () => { + ipcMain.on('open-gql', (event, args) => { + graphqlController.openConnection(event, args); + }); + + ipcMain.on('introspect', (event, introspectionObject) => { + graphqlController.introspect(event, introspectionObject); + }); +}; diff --git a/main_process/main_grpcController.js b/main_process/main_grpcController.js index 8cda66aae..03e93536b 100644 --- a/main_process/main_grpcController.js +++ b/main_process/main_grpcController.js @@ -1,276 +1,275 @@ -const { ipcMain } = require("electron"); -const grpc = require("@grpc/grpc-js"); -const protoLoader = require("@grpc/proto-loader"); -// ======================= grpcController.openGrpcConnection - -const testingController = require("./main_testingController"); - -const grpcController = {}; - -grpcController.openGrpcConnection = (event, reqResObj) => { - const { service, rpc, packageName, url, queryArr } = reqResObj; - - reqResObj.connectionType = "GRPC"; - reqResObj.response.times = []; - reqResObj.response.headers = {}; - reqResObj.response.events = []; - - // go through services object, find service where name matches our passed - // in service, then grab the rpc list of that service, also save that service - // let rpcList; - const services = reqResObj.servicesObj; - let foundService; - let rpcType; - let foundRpc; - - for (let i = 0; i < services.length; i++) { - if (services[i].name === service) { - foundService = services[i]; - // now loop through the rpcList and get our rpc along with rpc type - for (let j = 0; j < foundService.rpcs.length; j++) { - if (foundService.rpcs[j].name === rpc) { - foundRpc = foundService.rpcs[j]; - rpcType = foundRpc.type; - } - } - } - } - - const PROTO_PATH = reqResObj.protoPath; - const packageDefinition = protoLoader.loadSync(PROTO_PATH, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - }); - // create client credentials - const serverName = grpc.loadPackageDefinition(packageDefinition)[packageName]; - const client = new serverName[service]( - url, - grpc.credentials.createInsecure() - ); - - // create client requested metadata key and value pair for each type of streaming - const meta = new grpc.Metadata(); - // this is doing nothing right now - const metaArr = reqResObj.request.headers; - for (let i = 0; i < metaArr.length; i += 1) { - const currentHeader = metaArr[i]; - meta.add(currentHeader.key, currentHeader.value); - } - - if (rpcType === "UNARY") { - const query = reqResObj.queryArr[0]; - const time = {}; - - // Open Connection and set time sent for Unary - reqResObj.connection = "open"; - - time.timeSent = Date.now(); - // make Unary call - client[rpc](query, meta, (err, data) => { - if (err) { - console.log("unary error", err); - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - return; - } - // Close Connection and set time received for Unary - reqResObj.timeSent = time.timeSent; - - time.timeReceived = Date.now(); - reqResObj.timeReceived = time.timeReceived; - - reqResObj.connection = "closed"; - reqResObj.response.events.push(data); - reqResObj.response.times.push(time); - //check to see if there is a test script to run - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - data - ); - } - // send stuff back for store - event.sender.send("reqResUpdate", reqResObj); - }) // metadata from server - .on("metadata", (data) => { - // metadata is a Map, not an object - const metadata = data.internalRepr; - // set metadata Map as headers - metadata.forEach((value, key) => { - reqResObj.response.headers[key] = value[0]; - }); - event.sender.send("reqResUpdate", reqResObj); - }); - } else if (rpcType === "SERVER STREAM") { - const timesArr = []; - // Open Connection for SERVER Stream - reqResObj.connection = "open"; - reqResObj.timeSent = Date.now(); - const call = client[rpc](reqResObj.queryArr[0], meta); - call.on("data", (resp) => { - const time = {}; - time.timeReceived = Date.now(); - time.timeSent = reqResObj.timeSent; - reqResObj.response.times.push(time); - reqResObj.timeReceived = time.timeReceived; // overwritten on each call to get the final value - reqResObj.response.events.push(resp); - //check to see if there is a test script to run - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - resp - ); - } - event.sender.send("reqResUpdate", reqResObj); - }); - call.on("error", () => { - // for fatal error from server - console.log("server side stream error"); - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - }); - call.on("end", () => { - // Close Connection for SERVER Stream - if (reqResObj.connection !== "error") reqResObj.connection = "closed"; - // no need to push response to reqResObj, no event expected from on 'end' - event.sender.send("reqResUpdate", reqResObj); - }); - call.on("metadata", (data) => { - const metadata = data.internalRepr; - // set metadata Map as headers - metadata.forEach((value, key) => { - reqResObj.response.headers[key] = value[0]; - }); - event.sender.send("reqResUpdate", reqResObj); - }); - } else if (rpcType === "CLIENT STREAM") { - // create call and open client stream connection - reqResObj.connection = "open"; - const timeSent = Date.now(); - reqResObj.timeSent = timeSent; - const call = client[rpc](meta, function (error, response) { - if (error) { - console.log("error in client stream", error); - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - return; - } - //Close Connection for client Stream - reqResObj.connection = "closed"; - const curTime = Date.now(); - reqResObj.response.times.forEach((time) => { - time.timeReceived = curTime; - reqResObj.timeReceived = time.timeReceived; - }); - reqResObj.response.events.push(response); - //check to see if there is a test script to run - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - response - ); - } - // update state - event.sender.send("reqResUpdate", reqResObj); - }).on("metadata", (data) => { - // metadata is a Map, not an object - const metadata = data.internalRepr; - - metadata.forEach((value, key) => { - reqResObj.response.headers[key] = value[0]; - }); - event.sender.send("reqResUpdate", reqResObj); - }); - - for (let i = 0; i < queryArr.length; i++) { - const query = queryArr[i]; - // Open Connection for client Stream - // this needs additional work to provide correct sent time for each - // request without overwrite - const time = {}; - - reqResObj.connection = "pending"; - - time.timeSent = timeSent; - reqResObj.response.times.push(time); - call.write(query); - } - call.end(); - } - - //else BIDIRECTIONAL - else { - let counter = 0; - const call = client[rpc](meta); - call.on("data", (response) => { - const curTimeObj = reqResObj.response.times[counter]; - counter++; - //Close Individual Server Response for BIDIRECTIONAL Stream - reqResObj.connection = "pending"; - curTimeObj.timeReceived = Date.now(); - reqResObj.timeReceived = curTimeObj.timeReceived; - reqResObj.response.events.push(response); - reqResObj.response.times.push(curTimeObj); - //check to see if there is a test script to run - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj, - response - ); - } - // update redux store - event.sender.send("reqResUpdate", reqResObj); - }); // metadata from server - call.on("metadata", (data) => { - const metadata = data.internalRepr; - - metadata.forEach((value, key) => { - reqResObj.response.headers[key] = value[0]; - }); - event.sender.send("reqResUpdate", reqResObj); - }); - call.on("error", () => { - console.log("server ended connection with error"); - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - }); - call.on("end", (data) => { - //Close Final Server Connection for BIDIRECTIONAL Stream - if (reqResObj.connection !== "error") reqResObj.connection = "closed"; - // no need to push response to reqResObj, no event expected from on 'end' - event.sender.send("reqResUpdate", reqResObj); - }); - - for (let i = 0; i < queryArr.length; i++) { - const time = {}; - const query = queryArr[i]; - //Open Connection for BIDIRECTIONAL Stream - if (i === 0) { - reqResObj.connection = "open"; - } else { - reqResObj.connection = "pending"; - } - time.timeSent = Date.now(); - reqResObj.timeSent = time.timeSent; - reqResObj.response.times.push(time); - call.write(query); - } - call.end(); - } - event.sender.send("reqResUpdate", reqResObj); -}; - -module.exports = () => { - // creating our event listeners for IPC events - ipcMain.on("open-grpc", (event, reqResObj) => { - // we pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process - grpcController.openGrpcConnection(event, reqResObj); - }); -}; +const { ipcMain } = require('electron'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +const testingController = require('./main_testingController'); + +const grpcController = {}; + +grpcController.openGrpcConnection = (event, reqResObj) => { + const { service, rpc, packageName, url, queryArr } = reqResObj; + + reqResObj.connectionType = 'GRPC'; + reqResObj.response.times = []; + reqResObj.response.headers = {}; + reqResObj.response.events = []; + + // go through services object, find service where name matches our passed + // in service, then grab the rpc list of that service, also save that service + // let rpcList; + const services = reqResObj.servicesObj; + let foundService; + let rpcType; + let foundRpc; + + for (let i = 0; i < services.length; i++) { + if (services[i].name === service) { + foundService = services[i]; + // now loop through the rpcList and get our rpc along with rpc type + for (let j = 0; j < foundService.rpcs.length; j++) { + if (foundService.rpcs[j].name === rpc) { + foundRpc = foundService.rpcs[j]; + rpcType = foundRpc.type; + } + } + } + } + + const PROTO_PATH = reqResObj.protoPath; + const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + // create client credentials + const serverName = grpc.loadPackageDefinition(packageDefinition)[packageName]; + const client = new serverName[service]( + url, + grpc.credentials.createInsecure() + ); + + // create client requested metadata key and value pair for each type of streaming + const meta = new grpc.Metadata(); + // this is doing nothing right now + const metaArr = reqResObj.request.headers; + for (let i = 0; i < metaArr.length; i += 1) { + const currentHeader = metaArr[i]; + meta.add(currentHeader.key, currentHeader.value); + } + + if (rpcType === 'UNARY') { + const query = reqResObj.queryArr[0]; + const time = {}; + + // Open Connection and set time sent for Unary + reqResObj.connection = 'open'; + + time.timeSent = Date.now(); + // make Unary call + client[rpc](query, meta, (err, data) => { + if (err) { + console.log('unary error', err); + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + return; + } + // Close Connection and set time received for Unary + reqResObj.timeSent = time.timeSent; + + time.timeReceived = Date.now(); + reqResObj.timeReceived = time.timeReceived; + + reqResObj.connection = 'closed'; + reqResObj.response.events.push(data); + reqResObj.response.times.push(time); + // check to see if there is a test script to run + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + data + ); + } + // send stuff back for store + event.sender.send('reqResUpdate', reqResObj); + }) // metadata from server + .on('metadata', (data) => { + // metadata is a Map, not an object + const metadata = data.internalRepr; + // set metadata Map as headers + metadata.forEach((value, key) => { + reqResObj.response.headers[key] = value[0]; + }); + event.sender.send('reqResUpdate', reqResObj); + }); + } else if (rpcType === 'SERVER STREAM') { + const timesArr = []; + // Open Connection for SERVER Stream + reqResObj.connection = 'open'; + reqResObj.timeSent = Date.now(); + const call = client[rpc](reqResObj.queryArr[0], meta); + call.on('data', (resp) => { + const time = {}; + time.timeReceived = Date.now(); + time.timeSent = reqResObj.timeSent; + reqResObj.response.times.push(time); + reqResObj.timeReceived = time.timeReceived; // overwritten on each call to get the final value + reqResObj.response.events.push(resp); + // check to see if there is a test script to run + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + resp + ); + } + event.sender.send('reqResUpdate', reqResObj); + }); + call.on('error', () => { + // for fatal error from server + console.log('server side stream error'); + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + }); + call.on('end', () => { + // Close Connection for SERVER Stream + if (reqResObj.connection !== 'error') reqResObj.connection = 'closed'; + // no need to push response to reqResObj, no event expected from on 'end' + event.sender.send('reqResUpdate', reqResObj); + }); + call.on('metadata', (data) => { + const metadata = data.internalRepr; + // set metadata Map as headers + metadata.forEach((value, key) => { + reqResObj.response.headers[key] = value[0]; + }); + event.sender.send('reqResUpdate', reqResObj); + }); + } else if (rpcType === 'CLIENT STREAM') { + // create call and open client stream connection + reqResObj.connection = 'open'; + const timeSent = Date.now(); + reqResObj.timeSent = timeSent; + const call = client[rpc](meta, (error, response) => { + if (error) { + console.log('error in client stream', error); + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + return; + } + // Close Connection for client Stream + reqResObj.connection = 'closed'; + const curTime = Date.now(); + reqResObj.response.times.forEach((time) => { + time.timeReceived = curTime; + reqResObj.timeReceived = time.timeReceived; + }); + reqResObj.response.events.push(response); + // check to see if there is a test script to run + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + response + ); + } + // update state + event.sender.send('reqResUpdate', reqResObj); + }).on('metadata', (data) => { + // metadata is a Map, not an object + const metadata = data.internalRepr; + + metadata.forEach((value, key) => { + reqResObj.response.headers[key] = value[0]; + }); + event.sender.send('reqResUpdate', reqResObj); + }); + + for (let i = 0; i < queryArr.length; i++) { + const query = queryArr[i]; + // Open Connection for client Stream + // this needs additional work to provide correct sent time for each + // request without overwrite + const time = {}; + + reqResObj.connection = 'pending'; + + time.timeSent = timeSent; + reqResObj.response.times.push(time); + call.write(query); + } + call.end(); + } + + // else BIDIRECTIONAL + else { + let counter = 0; + const call = client[rpc](meta); + call.on('data', (response) => { + const curTimeObj = reqResObj.response.times[counter]; + counter++; + // Close Individual Server Response for BIDIRECTIONAL Stream + reqResObj.connection = 'pending'; + curTimeObj.timeReceived = Date.now(); + reqResObj.timeReceived = curTimeObj.timeReceived; + reqResObj.response.events.push(response); + reqResObj.response.times.push(curTimeObj); + // check to see if there is a test script to run + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj, + response + ); + } + // update redux store + event.sender.send('reqResUpdate', reqResObj); + }); // metadata from server + call.on('metadata', (data) => { + const metadata = data.internalRepr; + + metadata.forEach((value, key) => { + reqResObj.response.headers[key] = value[0]; + }); + event.sender.send('reqResUpdate', reqResObj); + }); + call.on('error', () => { + console.log('server ended connection with error'); + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + }); + call.on('end', (data) => { + // Close Final Server Connection for BIDIRECTIONAL Stream + if (reqResObj.connection !== 'error') reqResObj.connection = 'closed'; + // no need to push response to reqResObj, no event expected from on 'end' + event.sender.send('reqResUpdate', reqResObj); + }); + + for (let i = 0; i < queryArr.length; i++) { + const time = {}; + const query = queryArr[i]; + // Open Connection for BIDIRECTIONAL Stream + if (i === 0) { + reqResObj.connection = 'open'; + } else { + reqResObj.connection = 'pending'; + } + time.timeSent = Date.now(); + reqResObj.timeSent = time.timeSent; + reqResObj.response.times.push(time); + call.write(query); + } + call.end(); + } + event.sender.send('reqResUpdate', reqResObj); +}; + +module.exports = () => { + // creating our event listeners for IPC events + ipcMain.on('open-grpc', (event, reqResObj) => { + // we pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process + grpcController.openGrpcConnection(event, reqResObj); + }); +}; diff --git a/main_process/main_httpController.js b/main_process/main_httpController.js index 0efd8d85c..c47e5ee9c 100644 --- a/main_process/main_httpController.js +++ b/main_process/main_httpController.js @@ -1,482 +1,483 @@ -const { ipcMain } = require("electron"); -const fs = require("fs"); -const fetch2 = require("node-fetch"); -const http2 = require("http2"); -const setCookie = require("set-cookie-parser"); -const SSEController = require("./SSEController"); -const testingController = require("./main_testingController"); - -// Use this for HTTPS cert when in Dev or Test environment and -// using a server with self-signed cert on localhost -const LOCALHOST_CERT_PATH = "test/HTTP2_cert.pem"; - -const httpController = { - openHTTP2Connections: {}, - openHTTP2Streams: {}, - - // ---------------------------------------------------------------------------- - - openHTTPconnection(event, reqResObj) { - //console.log("event=>", event); - // HTTP2 currently only on HTTPS - if (reqResObj.protocol === "https://") { - httpController.establishHTTP2Connection(event, reqResObj); - } else { - httpController.establishHTTP1connection(event, reqResObj); - } - }, - - closeHTTPconnection(event, reqResObj) { - if (reqResObj.isHTTP2) this.closeHTTP2Stream(event, reqResObj); - else this.closeHTTP1Connection(event, reqResObj); - }, - - // ---------------------------------------------------------------------------- - - closeHTTP1Connection(event, reqResObj) { - if (reqResObj.request.isSSE) { - SSEController.closeConnection(reqResObj.id); - } - }, - - closeHTTP2Stream(event, reqResObj) { - const stream = this.openHTTP2Streams[reqResObj.id]; - if (stream) { - stream.close(); - delete this.openHTTP2Streams[reqResObj.id]; - } - }, - - establishHTTP2Connection(event, reqResObj) { - /* - Looks for existing HTTP2 connection in openHTTP2Connections. - If exists, use connection to initiate request - If not, create connection and save it to openHTTP2Connections, and then initiate request - */ - const { host } = reqResObj; - const foundHTTP2Connection = httpController.openHTTP2Connections[host]; - - // -------------------------------------------------- - // EXISTING HTTP2 CONNECTION IS FOUND ----- - // -------------------------------------------------- - if (foundHTTP2Connection) { - const { client } = foundHTTP2Connection; - - // periodically check connection status - // if destroyed or closed, remove from the conections collectoon and try to create a newhttp2 connection - // if failed (could be protocol error) move to HTTP1 and delete from http2 connections collection so user can try again - const interval = setInterval(() => { - if (client.destroyed || client.closed) { - clearInterval(interval); - delete httpController.openHTTP2Connections[host]; - httpController.openHTTPconnection(event, reqResObj); - } else if (foundHTTP2Connection.status === "failed") { - clearInterval(interval); - delete httpController.openHTTP2Connections[host]; - httpController.establishHTTP1connection(event, reqResObj); - } else if (foundHTTP2Connection.status === "connected") { - clearInterval(interval); - httpController.attachRequestToHTTP2Client(client, event, reqResObj); - } - }, 50); - - // if hasnt changed in 10 seconds, destroy client and clean up memory, send as error to front-end - setTimeout(() => { - clearInterval(interval); - if (foundHTTP2Connection.status === "initialized") { - client.destroy(); - delete httpController.openHTTP2Connections[host]; - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - } - }, 10000); - } - // -------------------------------------------------- - // NO EXISTING HTTP2 CONNECTION - make it before attaching request - // -------------------------------------------------- - else { - console.log("no pre-existing http2 found"); - - const clientOptions = {}; - // for self-signed certs on localhost in dev and test environments - if (host.includes("localhost")) { - clientOptions.ca = fs.readFileSync(LOCALHOST_CERT_PATH); - } - - const client = http2.connect(host, clientOptions, () => - console.log("connected!, reqRes.Obj.host", host) - ); - - // save HTTP2 connection to open connection collection - const http2Connection = { - client, - status: "initialized", - }; - httpController.openHTTP2Connections[host] = http2Connection; - - client.on("error", (err) => { - console.log("HTTP2 FAILED...trying HTTP1\n", err); - http2Connection.status = "failed"; - try { - client.destroy(); - } catch (error) { - console.log("error destroying HTTP2 client", error); - } - delete httpController.openHTTP2Connections[host]; - - // try again with fetch (HTTP1); - httpController.establishHTTP1connection(event, reqResObj); - }); - - client.on("connect", () => { - http2Connection.status = "connected"; - this.attachRequestToHTTP2Client(client, event, reqResObj); - }); - } - }, - - // ---------------------------------------------------------------------------- - - attachRequestToHTTP2Client(client, event, reqResObj) { - // initialize / clear response data and update front end - reqResObj.response.headers = {}; - reqResObj.response.events = []; - reqResObj.connection = "pending"; - reqResObj.timeSent = Date.now(); - event.sender.send("reqResUpdate", reqResObj); - - // format headers in chosen reqResObj so we can add them to our request - const formattedHeaders = {}; - reqResObj.request.headers.forEach((header) => { - formattedHeaders[header.key] = header.value; - }); - formattedHeaders[":path"] = reqResObj.path; - formattedHeaders[":method"] = reqResObj.request.method; - - // initiate request - // do not immediately close the *writable* side of the http2 stream - const reqStream = client.request(formattedHeaders, { - endStream: false, - }); - // save stream to collection for later access - this.openHTTP2Streams[reqResObj.id] = reqStream; - - //now close the writable side of our stream - if ( - reqResObj.request.method !== "GET" && - reqResObj.request.method !== "HEAD" - ) { - reqStream.end(reqResObj.request.body); - } else { - reqStream.end(); - } - - // persistent outside of listeners - let isSSE = false; - let data = ""; - - reqStream.setEncoding("utf8"); - - reqStream.on("response", (headers, flags) => { - // SSE will have 'stream' in the 'content-type' header - isSSE = - headers["content-type"] && headers["content-type"].includes("stream"); - - if (isSSE) { - reqResObj.connection = "open"; - reqResObj.connectionType = "SSE"; - } else { - reqResObj.connection = "closed"; - reqResObj.connectionType = "plain"; - } - - // Setting response size based on Content-length. Check if response comes with content-length - if (!headers["content-length"] && !headers["Content-Length"]) { - reqResObj.responseSize = null; - } else { - let contentLength; - headers["content-length"] - ? (contentLength = "content-length") - : (contentLength = "Content-Length"); - - // Converting content length octets into bytes - const conversionFigure = 1023.89427; - const octetToByteConversion = - headers[`${contentLength}`] / conversionFigure; - const responseSize = - Math.round((octetToByteConversion + Number.EPSILON) * 100) / 100; - reqResObj.responseSize = responseSize; - } - - // Content length is received in different letter cases. Whichever is returned will be used as the length for the calculation. - - reqResObj.isHTTP2 = true; - reqResObj.timeReceived = Date.now(); - reqResObj.response.headers = headers; - - // if cookies exists, parse the cookie(s) - if (headers["set-cookie"]) { - const parsedCookies = setCookie.parse(headers["set-cookie"]); - reqResObj.response.cookies = httpController.cookieFormatter( - parsedCookies - ); - } - event.sender.send("reqResUpdate", reqResObj); - }); - - reqStream.on("data", (chunk) => { - data += chunk; - - if (!isSSE) return; - const chunkTimestamp = Date.now(); - const dataEventArr = data.match(/[\s\S]*\n\n/g); - - while (dataEventArr && dataEventArr.length) { - const dataEvent = httpController.parseSSEFields(dataEventArr.shift()); - dataEvent.timeReceived = chunkTimestamp; - reqResObj.response.events.push(dataEvent); - event.sender.send("reqResUpdate", reqResObj); - - // recombine with \n\n to reconstruct original, minus what was already parsed. - data = dataEventArr.join("\n\n"); - } - }); - - reqStream.on("end", () => { - reqResObj.connection = "closed"; - delete httpController.openHTTP2Streams[reqResObj.id]; - - let dataEvent; - if (isSSE) { - dataEvent = httpController.parseSSEFields(data); - dataEvent.timeReceived = Date.now(); - } else if ( - data && - reqResObj.response.headers && - reqResObj.response.headers["content-type"] && - reqResObj.response.headers["content-type"].includes("application/json") - ) { - dataEvent = JSON.parse(data); - } else { - dataEvent = data; - } - reqResObj.response.events.push(dataEvent); - event.sender.send("reqResUpdate", reqResObj); - }); - }, - // ---------------------------------------------------------------------------- - - makeFetch(args, event, reqResObj) { - console.log("args===>", args); - console.log("event===>", event); - console.log("reqRESSSSOBJ===>", reqResObj); - return new Promise((resolve) => { - const { method, headers, body } = args.options; - - fetch2(headers.url, { method, headers, body }) - .then((response) => { - console.log("responsefromendpoint====>", response); - const headers = response.headers.raw(); - console.log("headersfromfetch==>", headers); - // check if the endpoint sends SSE - // add status-==== code for regular http requests in the response header - if (headers["content-type"][0].includes("stream")) { - // invoke another func that fetches to SSE and reads stream - // params: method, headers, body - resolve({ - headers, - body: { error: "This Is An SSE endpoint" }, - }); - } - headers[":status"] = response.status; - - const receivedCookie = headers["set-cookie"]; - console.log("receivedCookie===>", receivedCookie); - headers.cookies = receivedCookie; - console.log("newheaders==>", headers); - const contents = /json/.test(response.headers.get("content-type")) - ? response.json() - : response.text(); - - contents - .then((body) => { - console.log("bodyyyy====>", body); - - resolve({ - headers, - body, - }); - }) - .catch((error) => - console.log("ERROR from makeFetch contents", error) - ); - }) - .catch((error) => { - //error in connections - reqResObj.connection = "error"; - reqResObj.error = error; - // reqResObj.response.events.push(JSON.stringify(error)); - reqResObj.response.events.push(error); - event.sender.send("reqResUpdate", reqResObj); - }); - }); - }, - // ---------------------------------------------------------------------------- - - establishHTTP1connection(event, reqResObj) { - // initialize / clear response data and update front end - reqResObj.response.headers = {}; - reqResObj.response.events = []; - reqResObj.connection = "pending"; - reqResObj.timeSent = Date.now(); - - const options = this.parseFetchOptionsFromReqRes(reqResObj); - - //-------------------------------------------------------------------------------------------------------------- - // Check if the URL provided is a stream - //-------------------------------------------------------------------------------------------------------------- - if (reqResObj.request.isSSE) { - event.sender.send("reqResUpdate", reqResObj); - // if so, send us over to SSEController - SSEController.createStream(reqResObj, options, event); - // if not SSE, talk to main to fetch data and receive - } else { - this.makeFetch({ options }, event, reqResObj) - .then((response) => { - console.log("makefetchResponse===>", response); - // Parse response headers now to decide if SSE or not. - const heads = response.headers; - reqResObj.response.headers = heads; - reqResObj.connection = "closed"; - reqResObj.timeReceived = Date.now(); - reqResObj.response.status = heads[":status"]; - // send back reqResObj to renderer so it can update the redux store - - const theResponseHeaders = response.headers; - - const { body } = response; - reqResObj.response.headers = theResponseHeaders; - - // if cookies exists, parse the cookie(s) - if (setCookie.parse(theResponseHeaders.cookies)) { - reqResObj.response.cookies = this.cookieFormatter( - setCookie.parse(theResponseHeaders.cookies) - ); - } - // update reqres object to include new event - reqResObj = this.addSingleEvent(body, reqResObj); - console.log("latest REQRESOBJ=>", reqResObj); - // check if there is a test script to run - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj - ); - } - // send back reqResObj to renderer so it can update the redux store - event.sender.send("reqResUpdate", reqResObj); - }) - .catch((err) => { - reqResObj.connection = "error"; - // send back reqResObj to renderer so it can update the redux store - event.sender.send("reqResUpdate", reqResObj); - }); - } - }, - - // ---------------------------------------------------------------------------- - - parseFetchOptionsFromReqRes(reqResObject) { - const { headers, body, cookies } = reqResObject.request; - console.log("reqresOBJJJ=>", reqResObject); - - let { method } = reqResObject.request; - - method = method.toUpperCase(); - - const formattedHeaders = { - url: reqResObject.url, - }; - headers.forEach((head) => { - if (head.active) { - formattedHeaders[head.key] = head.value; - } - }); - - cookies.forEach((cookie) => { - const cookieString = `${cookie.key}=${cookie.value}`; - // attach to formattedHeaders so options object includes this - formattedHeaders.cookie = cookieString; - }); - - const outputObj = { - method, - mode: "cors", // no-cors, cors, *same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - credentials: "include", // include, *same-origin, omit - headers: formattedHeaders, - redirect: "follow", // manual, *follow, error - referrer: "no-referrer", // no-referrer, *client - }; - - if (method !== "GET" && method !== "HEAD") { - outputObj.body = body; - } - - return outputObj; - }, - - // ---------------------------------------------------------------------------- - - addSingleEvent(event, reqResObj) { - // adds new event to reqResObj and returns it so obj can be sent back to renderer process - reqResObj.timeReceived = Date.now(); - reqResObj.response.events.push(event); - reqResObj.connectionType = "plain"; - // returns updated reqResObj - return reqResObj; - }, - - cookieFormatter(cookieArray) { - return cookieArray.map((eachCookie) => { - const cookieFormat = { - name: eachCookie.name, - value: eachCookie.value, - domain: eachCookie.domain, - hostOnly: eachCookie.hostOnly ? eachCookie.hostOnly : false, - path: eachCookie.path, - secure: eachCookie.secure ? eachCookie.secure : false, - httpOnly: eachCookie.httpOnly ? eachCookie.httpOnly : false, - session: eachCookie.session ? eachCookie.session : false, - expirationDate: eachCookie.expires ? eachCookie.expires : "", - }; - return cookieFormat; - }); - }, - - // parses SSE format into an object - // SSE format -> 'key1: value1\nkey2: value2\nkey3: value3\n\n - // this separates fields by new lines and separates values from keys by removing the colon and the space - parseSSEFields(rawString) { - return rawString - .slice(0, -2) - .split("\n") - .reduce((obj, field) => { - const [key, value] = field.split(": "); - obj[key] = value; - return obj; - }, {}); - }, -}; - -module.exports = () => { - // creating our event listeners for IPC events - ipcMain.on("open-http", (event, reqResObj) => { - // we pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process - httpController.openHTTPconnection(event, reqResObj); - }); - - ipcMain.on("close-http", (event, reqResObj) => { - httpController.closeHTTPconnection(event, reqResObj); - }); -}; +const { ipcMain } = require('electron'); +const fs = require('fs'); +const fetch2 = require('node-fetch'); +const http2 = require('http2'); +const setCookie = require('set-cookie-parser'); +const SSEController = require('./SSEController'); +const testingController = require('./main_testingController'); + +// Use this for HTTPS cert when in Dev or Test environment and +// using a server with self-signed cert on localhost +const LOCALHOST_CERT_PATH = 'test/HTTP2_cert.pem'; + +const httpController = { + openHTTP2Connections: {}, + openHTTP2Streams: {}, + + // ---------------------------------------------------------------------------- + + openHTTPconnection(event, reqResObj) { + // console.log("event=>", event); + // HTTP2 currently only on HTTPS + if (reqResObj.protocol === 'https://') { + httpController.establishHTTP2Connection(event, reqResObj); + } else { + httpController.establishHTTP1connection(event, reqResObj); + } + }, + + closeHTTPconnection(event, reqResObj) { + if (reqResObj.isHTTP2) this.closeHTTP2Stream(event, reqResObj); + else this.closeHTTP1Connection(event, reqResObj); + }, + + // ---------------------------------------------------------------------------- + + closeHTTP1Connection(event, reqResObj) { + if (reqResObj.request.isSSE) { + SSEController.closeConnection(reqResObj.id); + } + }, + + closeHTTP2Stream(event, reqResObj) { + const stream = this.openHTTP2Streams[reqResObj.id]; + if (stream) { + stream.close(); + delete this.openHTTP2Streams[reqResObj.id]; + } + }, + + establishHTTP2Connection(event, reqResObj) { + /* + Looks for existing HTTP2 connection in openHTTP2Connections. + If exists, use connection to initiate request + If not, create connection and save it to openHTTP2Connections, and then initiate request + */ + const { host } = reqResObj; + const foundHTTP2Connection = httpController.openHTTP2Connections[host]; + + // -------------------------------------------------- + // EXISTING HTTP2 CONNECTION IS FOUND ----- + // -------------------------------------------------- + if (foundHTTP2Connection) { + const { client } = foundHTTP2Connection; + + // periodically check connection status + // if destroyed or closed, remove from the connections collection and try to create a new http2 connection + + // if failed (could be protocol error) move to HTTP1 and delete from http2 connections collection so user can try again + const interval = setInterval(() => { + if (client.destroyed || client.closed) { + clearInterval(interval); + delete httpController.openHTTP2Connections[host]; + httpController.openHTTPconnection(event, reqResObj); + } else if (foundHTTP2Connection.status === 'failed') { + clearInterval(interval); + delete httpController.openHTTP2Connections[host]; + httpController.establishHTTP1connection(event, reqResObj); + } else if (foundHTTP2Connection.status === 'connected') { + clearInterval(interval); + httpController.attachRequestToHTTP2Client(client, event, reqResObj); + } + }, 50); + + // if hasn't changed in 10 seconds, destroy client and clean up memory, send as error to front-end + setTimeout(() => { + clearInterval(interval); + if (foundHTTP2Connection.status === 'initialized') { + client.destroy(); + delete httpController.openHTTP2Connections[host]; + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + } + }, 10000); + } + // -------------------------------------------------- + // NO EXISTING HTTP2 CONNECTION - make it before attaching request + // -------------------------------------------------- + else { + console.log('no pre-existing http2 found'); + + const clientOptions = {}; + // for self-signed certs on localhost in dev and test environments + if (host.includes('localhost')) { + clientOptions.ca = fs.readFileSync(LOCALHOST_CERT_PATH); + } + + const client = http2.connect(host, clientOptions, () => + console.log('connected!, reqRes.Obj.host', host) + ); + + // save HTTP2 connection to open connection collection + const http2Connection = { + client, + status: 'initialized', + }; + httpController.openHTTP2Connections[host] = http2Connection; + + client.on('error', (err) => { + console.log('HTTP2 FAILED...trying HTTP1\n', err); + http2Connection.status = 'failed'; + try { + client.destroy(); + } catch (error) { + console.log('error destroying HTTP2 client', error); + } + delete httpController.openHTTP2Connections[host]; + + // try again with fetch (HTTP1); + httpController.establishHTTP1connection(event, reqResObj); + }); + + client.on('connect', () => { + http2Connection.status = 'connected'; + this.attachRequestToHTTP2Client(client, event, reqResObj); + }); + } + }, + + // ---------------------------------------------------------------------------- + + attachRequestToHTTP2Client(client, event, reqResObj) { + // initialize / clear response data and update front end + reqResObj.response.headers = {}; + reqResObj.response.events = []; + reqResObj.connection = 'pending'; + reqResObj.timeSent = Date.now(); + event.sender.send('reqResUpdate', reqResObj); + + // format headers in chosen reqResObj so we can add them to our request + const formattedHeaders = {}; + reqResObj.request.headers.forEach((header) => { + formattedHeaders[header.key] = header.value; + }); + formattedHeaders[':path'] = reqResObj.path; + formattedHeaders[':method'] = reqResObj.request.method; + + // initiate request + // do not immediately close the *writable* side of the http2 stream + const reqStream = client.request(formattedHeaders, { + endStream: false, + }); + // save stream to collection for later access + this.openHTTP2Streams[reqResObj.id] = reqStream; + + // now close the writable side of our stream + if ( + reqResObj.request.method !== 'GET' && + reqResObj.request.method !== 'HEAD' + ) { + reqStream.end(reqResObj.request.body); + } else { + reqStream.end(); + } + + // persistent outside of listeners + let isSSE = false; + let data = ''; + + reqStream.setEncoding('utf8'); + + reqStream.on('response', (headers, flags) => { + // SSE will have 'stream' in the 'content-type' header + isSSE = + headers['content-type'] && headers['content-type'].includes('stream'); + + if (isSSE) { + reqResObj.connection = 'open'; + reqResObj.connectionType = 'SSE'; + } else { + reqResObj.connection = 'closed'; + reqResObj.connectionType = 'plain'; + } + + // Setting response size based on Content-length. Check if response comes with content-length + if (!headers['content-length'] && !headers['Content-Length']) { + reqResObj.responseSize = null; + } else { + let contentLength; + headers['content-length'] + ? (contentLength = 'content-length') + : (contentLength = 'Content-Length'); + + // Converting content length octets into bytes + const conversionFigure = 1023.89427; + const octetToByteConversion = + headers[`${contentLength}`] / conversionFigure; + const responseSize = + Math.round((octetToByteConversion + Number.EPSILON) * 100) / 100; + reqResObj.responseSize = responseSize; + } + + // Content length is received in different letter cases. Whichever is returned will be used as the length for the calculation. + + reqResObj.isHTTP2 = true; + reqResObj.timeReceived = Date.now(); + reqResObj.response.headers = headers; + + // if cookies exists, parse the cookie(s) + if (headers['set-cookie']) { + const parsedCookies = setCookie.parse(headers['set-cookie']); + reqResObj.response.cookies = + httpController.cookieFormatter(parsedCookies); + } + event.sender.send('reqResUpdate', reqResObj); + }); + + reqStream.on('data', (chunk) => { + data += chunk; + + if (!isSSE) return; + const chunkTimestamp = Date.now(); + const dataEventArr = data.match(/[\s\S]*\n\n/g); + + while (dataEventArr && dataEventArr.length) { + const dataEvent = httpController.parseSSEFields(dataEventArr.shift()); + dataEvent.timeReceived = chunkTimestamp; + reqResObj.response.events.push(dataEvent); + event.sender.send('reqResUpdate', reqResObj); + + // recombine with \n\n to reconstruct original, minus what was already parsed. + data = dataEventArr.join('\n\n'); + } + }); + + reqStream.on('end', () => { + reqResObj.connection = 'closed'; + delete httpController.openHTTP2Streams[reqResObj.id]; + + let dataEvent; + if (isSSE) { + dataEvent = httpController.parseSSEFields(data); + dataEvent.timeReceived = Date.now(); + } else if ( + data && + reqResObj.response.headers && + reqResObj.response.headers['content-type'] && + reqResObj.response.headers['content-type'].includes('application/json') + ) { + dataEvent = JSON.parse(data); + } else { + dataEvent = data; + } + reqResObj.response.events.push(dataEvent); + event.sender.send('reqResUpdate', reqResObj); + }); + }, + + // ---------------------------------------------------------------------------- + + makeFetch(args, event, reqResObj) { + console.log('args===>', args); + console.log('event===>', event); + console.log('reqRESSSSOBJ===>', reqResObj); + return new Promise((resolve) => { + const { method, headers, body } = args.options; + + fetch2(headers.url, { method, headers, body }) + .then((response) => { + console.log('responsefromendpoint====>', response); + const headers = response.headers.raw(); + console.log('headersfromfetch==>', headers); + // check if the endpoint sends SSE + // add status-==== code for regular http requests in the response header + if (headers['content-type'][0].includes('stream')) { + // invoke another func that fetches to SSE and reads stream + // params: method, headers, body + resolve({ + headers, + body: { error: 'This Is An SSE endpoint' }, + }); + } + headers[':status'] = response.status; + + const receivedCookie = headers['set-cookie']; + console.log('receivedCookie===>', receivedCookie); + headers.cookies = receivedCookie; + console.log('newheaders==>', headers); + const contents = /json/.test(response.headers.get('content-type')) + ? response.json() + : response.text(); + + contents + .then((body) => { + console.log('bodyyyy====>', body); + + resolve({ + headers, + body, + }); + }) + .catch((error) => + console.log('ERROR from makeFetch contents', error) + ); + }) + .catch((error) => { + // error in connections + reqResObj.connection = 'error'; + reqResObj.error = error; + // reqResObj.response.events.push(JSON.stringify(error)); + reqResObj.response.events.push(error); + event.sender.send('reqResUpdate', reqResObj); + }); + }); + }, + // ---------------------------------------------------------------------------- + + establishHTTP1connection(event, reqResObj) { + // initialize / clear response data and update front end + reqResObj.response.headers = {}; + reqResObj.response.events = []; + reqResObj.connection = 'pending'; + reqResObj.timeSent = Date.now(); + + const options = this.parseFetchOptionsFromReqRes(reqResObj); + + //----------------------------------------- + // Check if the URL provided is a stream + //----------------------------------------- + if (reqResObj.request.isSSE) { + event.sender.send('reqResUpdate', reqResObj); + // if so, send us over to SSEController + SSEController.createStream(reqResObj, options, event); + // if not SSE, talk to main to fetch data and receive + } else { + this.makeFetch({ options }, event, reqResObj) + .then((response) => { + console.log('makefetchResponse===>', response); + // Parse response headers now to decide if SSE or not. + const heads = response.headers; + reqResObj.response.headers = heads; + reqResObj.connection = 'closed'; + reqResObj.timeReceived = Date.now(); + reqResObj.response.status = heads[':status']; + // send back reqResObj to renderer so it can update the redux store + + const theResponseHeaders = response.headers; + + const { body } = response; + reqResObj.response.headers = theResponseHeaders; + + // if cookies exists, parse the cookie(s) + if (setCookie.parse(theResponseHeaders.cookies)) { + reqResObj.response.cookies = this.cookieFormatter( + setCookie.parse(theResponseHeaders.cookies) + ); + } + // update reqres object to include new event + reqResObj = this.addSingleEvent(body, reqResObj); + console.log('latest REQRESOBJ=>', reqResObj); + // check if there is a test script to run + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj + ); + } + // send back reqResObj to renderer so it can update the redux store + event.sender.send('reqResUpdate', reqResObj); + }) + .catch((err) => { + reqResObj.connection = 'error'; + // send back reqResObj to renderer so it can update the redux store + event.sender.send('reqResUpdate', reqResObj); + }); + } + }, + + // ---------------------------------------------------------------------------- + + parseFetchOptionsFromReqRes(reqResObject) { + const { headers, body, cookies } = reqResObject.request; + console.log('reqresOBJJJ=>', reqResObject); + + let { method } = reqResObject.request; + + method = method.toUpperCase(); + + const formattedHeaders = { + url: reqResObject.url, + }; + headers.forEach((head) => { + if (head.active) { + formattedHeaders[head.key] = head.value; + } + }); + + cookies.forEach((cookie) => { + const cookieString = `${cookie.key}=${cookie.value}`; + // attach to formattedHeaders so options object includes this + formattedHeaders.cookie = cookieString; + }); + + const outputObj = { + method, + mode: 'cors', // no-cors, cors, *same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'include', // include, *same-origin, omit + headers: formattedHeaders, + redirect: 'follow', // manual, *follow, error + referrer: 'no-referrer', // no-referrer, *client + }; + + if (method !== 'GET' && method !== 'HEAD') { + outputObj.body = body; + } + + return outputObj; + }, + + // ---------------------------------------------------------------------------- + + addSingleEvent(event, reqResObj) { + // adds new event to reqResObj and returns it so obj can be sent back to renderer process + reqResObj.timeReceived = Date.now(); + reqResObj.response.events.push(event); + reqResObj.connectionType = 'plain'; + // returns updated reqResObj + return reqResObj; + }, + + cookieFormatter(cookieArray) { + return cookieArray.map((eachCookie) => { + const cookieFormat = { + name: eachCookie.name, + value: eachCookie.value, + domain: eachCookie.domain, + hostOnly: eachCookie.hostOnly ? eachCookie.hostOnly : false, + path: eachCookie.path, + secure: eachCookie.secure ? eachCookie.secure : false, + httpOnly: eachCookie.httpOnly ? eachCookie.httpOnly : false, + session: eachCookie.session ? eachCookie.session : false, + expirationDate: eachCookie.expires ? eachCookie.expires : '', + }; + return cookieFormat; + }); + }, + + // parses SSE format into an object + // SSE format -> 'key1: value1\nkey2: value2\nkey3: value3\n\n + // this separates fields by new lines and separates values from keys by removing the colon and the space + parseSSEFields(rawString) { + return rawString + .slice(0, -2) + .split('\n') + .reduce((obj, field) => { + const [key, value] = field.split(': '); + obj[key] = value; + return obj; + }, {}); + }, +}; + +module.exports = () => { + // creating our event listeners for IPC events + ipcMain.on('open-http', (event, reqResObj) => { + // we pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process + httpController.openHTTPconnection(event, reqResObj); + }); + + ipcMain.on('close-http', (event, reqResObj) => { + httpController.closeHTTPconnection(event, reqResObj); + }); +}; diff --git a/main_process/main_testingController.js b/main_process/main_testingController.js index e994f7a9f..d857c50ae 100644 --- a/main_process/main_testingController.js +++ b/main_process/main_testingController.js @@ -1,103 +1,104 @@ -const { NodeVM } = require("vm2"); -const chai = require("chai"); - -const testHttpController = {}; - -testHttpController.runTest = ( - inputScript, - reqResObj, - protocolData, - isGraphQL = false -) => { - const { request } = reqResObj; - let { response } = reqResObj; - // final test result objects will be stored in this array - const testResults = []; - - if (isGraphQL) { - const data = protocolData.data; - const events = data; - response = { ...response, events }; - } - // this is the global object that will be passed into the VM - const sandbox = { - // function to push an Assertion object into the array - addOneResult: (result) => testResults.push(result), - request, - response, - chai, - }; - // create a new instance of a secure Node VM - const vm = new NodeVM({ - sandbox, - // specify external libraries to be required in (dev mode only) - require: { - external: [], - }, - }); - // the regex matches all 'assert' or 'expect' on seperate lines - // it will also match all variables - // eslint-disable-next-line no-useless-escape - const testRegex = /(((const|let|var)\s+\w*\s*=\s*(\'[^\']*\'|\"[^\"]*\"|\w*[^\s\;]*))|(expect|assert)[^;\n]*\([^;\n]*\)[\w\.]*)/gm; - const separatedScriptsArray = inputScript.match(testRegex) ?? []; - - // create an array of test scripts that will be executed in Node VM instance - const arrOfTestScripts = separatedScriptsArray.map((script) => { - // construct and return the individual test script - // if the assertion test does not fail, then push an object with the message and status - // to the results array - // if the assertion test fails and throws an error, also include the expected and actual - // create a variable to conditionally declare in the right scope of the test script - let variables = ""; - if (/^let|^const|^var/.test(script)) { - variables = script; - } - - //assert.strictEqual(response.status, 200, 'response is 200') - return ` - ${variables} - try { - ${script} - if(!/^let|^const|^var/.test(${JSON.stringify(script)})){ - addOneResult({ - message: ${JSON.stringify(script)}, - status: 'PASS', - }); - } - } catch (err) { - const errObj = err; - - addOneResult({ - message: errObj.message, - status: 'FAIL', - expected: errObj.expected, - actual: errObj.actual, - }); - } - `; - }); - // require in the chai assertion library - // then concatenate all the scripts to the testScript string - const testScript = ` - const { assert, expect } = chai; - ${arrOfTestScripts.join(" ")} - `; - try { - // run the script in the VM - // the second argument denotes where the vm should look for the node_modules folder - // that is, relative to the main.js file where the electron process is running - vm.run(testScript, "main.js"); - // deep clone the testResults array since sending functions, DOM elements, and non-cloneable - // JS objects is not supported IPC channels past Electron 9 - return JSON.parse(JSON.stringify(testResults)); - } catch (err) { - console.log( - "caught error!: in the catch block of main_testController.js", - err - ); - // return a null object in the event of an error - return null; - } -}; - -module.exports = testHttpController; +/* eslint-disable no-useless-escape */ +const { NodeVM } = require('vm2'); +const chai = require('chai'); + +const testHttpController = {}; + +testHttpController.runTest = ( + inputScript, + reqResObj, + protocolData, + isGraphQL = false +) => { + const { request } = reqResObj; + let { response } = reqResObj; + // final test result objects will be stored in this array + const testResults = []; + + if (isGraphQL) { + const { data } = protocolData; + const events = data; + response = { ...response, events }; + } + // this is the global object that will be passed into the VM + const sandbox = { + // function to push an Assertion object into the array + addOneResult: (result) => testResults.push(result), + request, + response, + chai, + }; + // create a new instance of a secure Node VM + const vm = new NodeVM({ + sandbox, + // specify external libraries to be required in (dev mode only) + require: { + external: [], + }, + }); + // the regex matches all 'assert' or 'expect' on separate lines + // it will also match all variables + // eslint-disable-next-line no-useless-escape + const testRegex = + /(((const|let|var)\s+\w*\s*=\s*(\'[^\']*\'|\"[^\"]*\"|\w*[^\s\;]*))|(expect|assert)[^;\n]*\([^;\n]*\)[\w\.]*)/gm; + const separatedScriptsArray = inputScript.match(testRegex) ?? []; + + // create an array of test scripts that will be executed in Node VM instance + const arrOfTestScripts = separatedScriptsArray.map((script) => { + // construct and return the individual test script + // if the assertion test does not fail, then push an object with the message and status + // to the results array + // if the assertion test fails and throws an error, also include the expected and actual + // create a variable to conditionally declare in the right scope of the test script + let variables = ''; + if (/^let|^const|^var/.test(script)) { + variables = script; + } + + return ` + ${variables} + try { + ${script} + if(!/^let|^const|^var/.test(${JSON.stringify(script)})){ + addOneResult({ + message: ${JSON.stringify(script)}, + status: 'PASS', + }); + } + } catch (err) { + const errObj = err; + + addOneResult({ + message: errObj.message, + status: 'FAIL', + expected: errObj.expected, + actual: errObj.actual, + }); + } + `; + }); + // require in the chai assertion library + // then concatenate all the scripts to the testScript string + const testScript = ` + const { assert, expect } = chai; + ${arrOfTestScripts.join(' ')} + `; + try { + // run the script in the VM + // the second argument denotes where the vm should look for the node_modules folder + // that is, relative to the main.js file where the electron process is running + vm.run(testScript, 'main.js'); + // deep clone the testResults array since sending functions, DOM elements, and non-cloneable + // JS objects is not supported IPC channels past Electron 9 + return JSON.parse(JSON.stringify(testResults)); + } catch (err) { + console.log( + 'caught error!: in the catch block of main_testController.js', + err + ); + // return a null object in the event of an error + return null; + } +}; + +module.exports = testHttpController; diff --git a/main_process/main_touchbar.js b/main_process/main_touchbar.js index 614579211..1a460d803 100644 --- a/main_process/main_touchbar.js +++ b/main_process/main_touchbar.js @@ -1,87 +1,87 @@ -const { TouchBar } = require("electron"); -// TouchBarButtons are our nav buttons(ex: Select All, Deselect All, Open Selected, Close Selected, Clear All) - -const { TouchBarButton, TouchBarSpacer } = TouchBar; - -/**************************** - ** Create Touchbar buttons ** - *****************************/ - -const tbSelectAllButton = new TouchBarButton({ - label: "Select All", - backgroundColor: "#3DADC2", - click: () => { - mainWindow.webContents.send("selectAll"); - }, -}); - -const tbDeselectAllButton = new TouchBarButton({ - label: "Deselect All", - backgroundColor: "#3DADC2", - click: () => { - mainWindow.webContents.send("deselectAll"); - }, -}); - -const tbOpenSelectedButton = new TouchBarButton({ - label: "Open Selected", - backgroundColor: "#00E28B", - click: () => { - mainWindow.webContents.send("openAllSelected"); - }, -}); - -const tbCloseSelectedButton = new TouchBarButton({ - label: "Close Selected", - backgroundColor: "#DB5D58", - click: () => { - mainWindow.webContents.send("closeAllSelected"); - }, -}); - -const tbMinimizeAllButton = new TouchBarButton({ - label: "Minimize All", - backgroundColor: "#3DADC2", - click: () => { - mainWindow.webContents.send("minimizeAll"); - }, -}); - -const tbExpandAllButton = new TouchBarButton({ - label: "Expand All", - backgroundColor: "#3DADC2", - click: () => { - mainWindow.webContents.send("expandedAll"); - }, -}); - -const tbClearAllButton = new TouchBarButton({ - label: "Clear All", - backgroundColor: "#708090", - click: () => { - mainWindow.webContents.send("clearAll"); - }, -}); - -const tbSpacer = new TouchBarSpacer(); - -const tbFlexSpacer = new TouchBarSpacer({ - size: "flexible", -}); - -/******************************** - ** Attach buttons to touchbar ** - ********************************/ - -const touchBar = new TouchBar([ - tbSpacer, - tbSelectAllButton, - tbDeselectAllButton, - tbOpenSelectedButton, - tbCloseSelectedButton, - tbMinimizeAllButton, - tbExpandAllButton, - tbClearAllButton, -]); - -module.exports = touchBar; +const { TouchBar } = require('electron'); +// TouchBarButtons are our nav buttons(ex: Select All, Deselect All, Open Selected, Close Selected, Clear All) + +const { TouchBarButton, TouchBarSpacer } = TouchBar; + +/** ************************** + ** Create Touchbar buttons ** + **************************** */ + +const tbSelectAllButton = new TouchBarButton({ + label: 'Select All', + backgroundColor: '#3DADC2', + click: () => { + mainWindow.webContents.send('selectAll'); + }, +}); + +const tbDeselectAllButton = new TouchBarButton({ + label: 'Deselect All', + backgroundColor: '#3DADC2', + click: () => { + mainWindow.webContents.send('deselectAll'); + }, +}); + +const tbOpenSelectedButton = new TouchBarButton({ + label: 'Open Selected', + backgroundColor: '#00E28B', + click: () => { + mainWindow.webContents.send('openAllSelected'); + }, +}); + +const tbCloseSelectedButton = new TouchBarButton({ + label: 'Close Selected', + backgroundColor: '#DB5D58', + click: () => { + mainWindow.webContents.send('closeAllSelected'); + }, +}); + +const tbMinimizeAllButton = new TouchBarButton({ + label: 'Minimize All', + backgroundColor: '#3DADC2', + click: () => { + mainWindow.webContents.send('minimizeAll'); + }, +}); + +const tbExpandAllButton = new TouchBarButton({ + label: 'Expand All', + backgroundColor: '#3DADC2', + click: () => { + mainWindow.webContents.send('expandedAll'); + }, +}); + +const tbClearAllButton = new TouchBarButton({ + label: 'Clear All', + backgroundColor: '#708090', + click: () => { + mainWindow.webContents.send('clearAll'); + }, +}); + +const tbSpacer = new TouchBarSpacer(); + +const tbFlexSpacer = new TouchBarSpacer({ + size: 'flexible', +}); + +/** ****************************** + ** Attach buttons to touchbar ** + ******************************* */ + +const touchBar = new TouchBar([ + tbSpacer, + tbSelectAllButton, + tbDeselectAllButton, + tbOpenSelectedButton, + tbCloseSelectedButton, + tbMinimizeAllButton, + tbExpandAllButton, + tbClearAllButton, +]); + +module.exports = touchBar; diff --git a/main_process/main_wsController.js b/main_process/main_wsController.js index 3cc249010..cc7c466c5 100644 --- a/main_process/main_wsController.js +++ b/main_process/main_wsController.js @@ -1,173 +1,173 @@ -const { ipcMain } = require("electron"); -const { dialog } = require("electron"); -// const store = require('./src/client/store.js') -const WebSocketClient = require("websocket").client; -const fs = require("fs"); -const path = require("path"); -const testingController = require("./main_testingController"); - -const wsController = { - wsConnect: null, - openWSconnection(event, reqResObj, connectionArray) { - //set reqResObj for WS - reqResObj.response.messages = []; - reqResObj.request.messages = []; - reqResObj.connection = "pending"; - reqResObj.closeCode = 0; - reqResObj.timeSent = Date.now(); - - //update frontend its pending - event.sender.send("reqResUpdate", reqResObj); - - //create socket - //check websocket npm package doc - let socket; - try { - socket = new WebSocketClient(); - } catch (err) { - reqResObj.connection = "error"; - event.sender.send("reqResUpdate", reqResObj); - return; - } - - //when it connects, update connectionArray - //connection here means a single connection being established - socket.on("connect", (connection) => { - console.log("websocket client connected"); - this.wsConnect = connection; - reqResObj.connection = "open"; - reqResObj.response.connection = "open"; - // testingController.runTest(reqResObj.request.testContent, reqResObj); - const openConnectionObj = { - connection, - protocol: "WS", - id: reqResObj.id, - }; - connectionArray.push(openConnectionObj); - event.sender.send("update-connectionArray", connectionArray); - event.sender.send("reqResUpdate", reqResObj); - //connection.on - this.wsConnect.on("close", () => { - console.log("closed WS"); - }); - }); - - //listener for failed socket connection, - socket.on("connectFailed", (error) => { - console.log("WS Connect Error: " + error.toString()); - reqResObj.connection = "error"; - reqResObj.timeReceived = Date.now(); - // reqResObj.response.events.push(JSON.stringify(errorsObj)); need to move - event.sender.send("reqResUpdate", reqResObj); - }); - - //connect socket - socket.connect(reqResObj.url); - }, - - closeWs(event) { - //connection.close - this.wsConnect.close(); - }, - - sendWebSocketMessage(event, reqResObj, inputMessage) { - //send message to ws server - //connection.send - - //check datatype - - if (inputMessage.includes("data:image/")) { - const buffer = Buffer.from(inputMessage, "utf8"); - console.log("sending as buffer"); - this.wsConnect.sendBytes(buffer); - reqResObj.request.messages.push({ - data: buffer, - timeReceived: Date.now(), - }); - } else { - this.wsConnect.send(inputMessage); - console.log("sending as string"); - reqResObj.request.messages.push({ - data: inputMessage, - timeReceived: Date.now(), - }); - } - - //update store - event.sender.send("reqResUpdate", reqResObj); - - //listener for return message from ws server - //push into message array under responses - //connection.on - this.wsConnect.on("message", (e) => { - e.binaryData - ? reqResObj.response.messages.push({ - data: e.binaryData, - timeReceived: Date.now(), - }) - : reqResObj.response.messages.push({ - data: e.utf8Data, - timeReceived: Date.now(), - }); - - if (reqResObj.request.testContent) { - reqResObj.response.testResult = testingController.runTest( - reqResObj.request.testContent, - reqResObj - ); - console.log("the test result", reqResObj.response.testResult); - } - - //update store - event.sender.send("reqResUpdate", reqResObj); - }); - }, -}; -module.exports = () => { - // we pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process - // listener to open wsconnection - ipcMain.on("open-ws", (event, reqResObj, connectionArray) => { - wsController.openWSconnection(event, reqResObj, connectionArray); - }); - //listener for sending messages to server - ipcMain.on("send-ws", (event, reqResObj, inputMessage) => { - wsController.sendWebSocketMessage(event, reqResObj, inputMessage); - }); - //listerner to close socket connection - ipcMain.on("close-ws", (event) => { - wsController.closeWs(event); - }); - ipcMain.on("exportChatLog", (event, outgoingMessages, incomingMessages) => { - //making sure the messages are in order - let result = outgoingMessages - .map((message) => { - message.source = "client"; - return message; - }) - .concat( - incomingMessages.map((message) => { - message.source = "server"; - return message; - }) - ) - .sort((a, b) => a.timeReceived - b.timeReceived) - .map((message, index) => ({ - index: index, - source: message.source, - data: message.data, - timeReceived: message.timeReceived, - })); - - const data = new Uint8Array(Buffer.from(JSON.stringify(result))); - - //showSaveDialog is the windowexplorer that appears - dialog - .showSaveDialog({ defaultPath: "websocketLog.txt" }) - .then((file_path) => { - fs.writeFile(file_path.filePath, data, (err) => { - if (err) throw err; - console.log("File saved to: ", file_path.filePath); - }); - }); - }); -}; +/* eslint-disable camelcase */ +const { ipcMain } = require('electron'); +const { dialog } = require('electron'); +const WebSocketClient = require('websocket').client; +const fs = require('fs'); +const testingController = require('./main_testingController'); + +const wsController = { + wsConnect: null, + openWSconnection(event, reqResObj, connectionArray) { + // set reqResObj for WS + reqResObj.response.messages = []; + reqResObj.request.messages = []; + reqResObj.connection = 'pending'; + reqResObj.closeCode = 0; + reqResObj.timeSent = Date.now(); + + // update frontend its pending + event.sender.send('reqResUpdate', reqResObj); + + // create socket + // check websocket npm package doc + let socket; + try { + socket = new WebSocketClient(); + } catch (err) { + reqResObj.connection = 'error'; + event.sender.send('reqResUpdate', reqResObj); + return; + } + + // when it connects, update connectionArray + // connection here means a single connection being established + socket.on('connect', (connection) => { + console.log('websocket client connected'); + this.wsConnect = connection; + reqResObj.connection = 'open'; + reqResObj.response.connection = 'open'; + + const openConnectionObj = { + connection, + protocol: 'WS', + id: reqResObj.id, + }; + connectionArray.push(openConnectionObj); + event.sender.send('update-connectionArray', connectionArray); + event.sender.send('reqResUpdate', reqResObj); + + // connection.on + this.wsConnect.on('close', () => { + console.log('closed WS'); + }); + }); + + // listener for failed socket connection, + socket.on('connectFailed', (error) => { + console.log(`WS Connect Error: ${error.toString()}`); + reqResObj.connection = 'error'; + reqResObj.timeReceived = Date.now(); + // reqResObj.response.events.push(JSON.stringify(errorsObj)); need to move + event.sender.send('reqResUpdate', reqResObj); + }); + + // connect socket + socket.connect(reqResObj.url); + }, + + // close connection + closeWs(event) { + this.wsConnect.close(); + }, + + sendWebSocketMessage(event, reqResObj, inputMessage) { + // check datatype + if (inputMessage.includes('data:image/')) { + const buffer = Buffer.from(inputMessage, 'utf8'); + console.log('sending as buffer'); + this.wsConnect.sendBytes(buffer); + reqResObj.request.messages.push({ + data: buffer, + timeReceived: Date.now(), + }); + } else { + this.wsConnect.send(inputMessage); + console.log('sending as string'); + reqResObj.request.messages.push({ + data: inputMessage, + timeReceived: Date.now(), + }); + } + + // update store + event.sender.send('reqResUpdate', reqResObj); + + // listener for return message from ws server + // push into message array under responses + // connection.on + this.wsConnect.on('message', (e) => { + e.binaryData + ? reqResObj.response.messages.push({ + data: e.binaryData, + timeReceived: Date.now(), + }) + : reqResObj.response.messages.push({ + data: e.utf8Data, + timeReceived: Date.now(), + }); + + if (reqResObj.request.testContent) { + reqResObj.response.testResult = testingController.runTest( + reqResObj.request.testContent, + reqResObj + ); + console.log('the test result', reqResObj.response.testResult); + } + + // update store + event.sender.send('reqResUpdate', reqResObj); + }); + }, +}; +module.exports = () => { + // pass the event object into these controller functions so that we can invoke event.sender.send when we need to make response to renderer process + + // listener to open wsconnection + ipcMain.on('open-ws', (event, reqResObj, connectionArray) => { + wsController.openWSconnection(event, reqResObj, connectionArray); + }); + + // listener for sending messages to server + ipcMain.on('send-ws', (event, reqResObj, inputMessage) => { + wsController.sendWebSocketMessage(event, reqResObj, inputMessage); + }); + + // listener to close socket connection + ipcMain.on('close-ws', (event) => { + wsController.closeWs(event); + }); + + ipcMain.on('exportChatLog', (event, outgoingMessages, incomingMessages) => { + // making sure the messages are in order + const result = outgoingMessages + .map((message) => { + message.source = 'client'; + return message; + }) + .concat( + incomingMessages.map((message) => { + message.source = 'server'; + return message; + }) + ) + .sort((a, b) => a.timeReceived - b.timeReceived) + .map((message, index) => ({ + index, + source: message.source, + data: message.data, + timeReceived: message.timeReceived, + })); + + const data = new Uint8Array(Buffer.from(JSON.stringify(result))); + + // showSaveDialog is the window explorer that appears + dialog + .showSaveDialog({ defaultPath: 'websocketLog.txt' }) + .then((file_path) => { + fs.writeFile(file_path.filePath, data, (err) => { + if (err) throw err; + console.log('File saved to: ', file_path.filePath); + }); + }); + }); +}; diff --git a/main_process/openapiParser.js b/main_process/openapiParser.js new file mode 100644 index 000000000..51c9367c7 --- /dev/null +++ b/main_process/openapiParser.js @@ -0,0 +1,56 @@ +const YAML = require('yamljs'); + +// TODO: Validation, Callbacks +const openapiParserFunc = (input) => { + if (input === undefined || input === null) + ReferenceError('OpenAPI Document not found.'); + let doc; + try { + doc = JSON.parse(input); + } catch (SyntaxError) { + doc = YAML.parse(input); + console.error(SyntaxError); + } + const { info, servers, tags, paths, components } = doc; + info.openapi = doc.openapi; + const serverUrls = [...servers.map((server) => server.url)]; + let id = 0; + + const openapiReqArray = []; + Object.entries(paths).forEach(([endpoint, pathObj]) => { + Object.entries(pathObj).forEach(([method, operationObj]) => { + id += 1; + const { + summary, + description, + operationId, + tags, + parameters, // security + } = operationObj; + + const request = { + id, + // enabled: true, + reqTags: tags, + summary, + description, + operationId, + method: method.toUpperCase(), + reqServers: [], + endpoint, + parameters, + body: new Map(), + headers: {}, + cookies: {}, + // params: {}, + // queries: {}, + urls: [], + }; + openapiReqArray.push(request); + }); + }); + const openapiMetadata = { info, tags, serverUrls }; + return { openapiMetadata, openapiReqArray }; +}; + +module.exports = openapiParserFunc; diff --git a/main_process/protoParser.js b/main_process/protoParser.js index 71b72ab03..6fd0d80c7 100644 --- a/main_process/protoParser.js +++ b/main_process/protoParser.js @@ -1,161 +1,165 @@ -// this file is being required into main. this function, protoParserFunc, should only be called inside main. - -const fs = require("fs"); -const grpc = require("@grpc/grpc-js"); -const protoLoader = require("@grpc/proto-loader"); -const path = require("path"); - -async function protoParserFunc(protoBodyData) { - // define storage for .proto parsed content - const protoStorage = {}; - //store the original .proto content in the storage before parsing - protoStorage.protoMaster = protoBodyData; - //make unique protoID for file we are saving - let protoID = Math.floor(Math.random() * 10000); - //if file path for that ID already exists, increment the ID - try { - if (!fs.existsSync(path.join(process.resourcesPath, "/protos/"))) { - fs.mkdirSync(path.join(process.resourcesPath, "/protos/")); - } - } catch (err) { - console.error(err); - } - - try { - while ( - fs.existsSync( - path.join(process.resourcesPath, "/protos/" + protoID + ".proto") - ) - ) { - //if file name exists try incrementing by 1 - protoID += 1; - } - } catch (err) { - console.error(err); - } - - // write to saveProto file for interaction with the server - fs.writeFileSync( - path.join(process.resourcesPath, "/protos/" + protoID + ".proto"), - protoBodyData, - "utf-8" - ); - - // define the modular client for testing - // declare path variable of imported proto file - const PROTO_PATH = path.join( - process.resourcesPath, - "/protos/" + protoID + ".proto" - ); - // store the proto Path on proto storage object - protoStorage.protoPath = PROTO_PATH; - - // create gRPC package options - const protoOptionsObj = { - keepCase: true, - enums: String, - longs: String, - defaults: true, - oneofs: true, - }; - - //create gRPC package definition w/ protoLoader library - protoStorage.packageDefinition = protoLoader.loadSync( - PROTO_PATH, - protoOptionsObj - ); - - //create descriptor from package packageDefinition - //descriptor --> gRPC uses the Protobuf .proto file format to define your messages, services and some aspects of the code generation. - protoStorage.descriptor = grpc.loadPackageDefinition( - protoStorage.packageDefinition - ); - protoStorage.packageName = Object.keys(protoStorage.descriptor)[0]; - - // a recursive helper function to find our service definition. if it's nested, we need to find the actual rpc data - const findNestedService = (obj) => { - if (Object.values(obj).length > 1) return obj; // otherwise... - return findNestedService(Object.values(obj)[0]) - } - // invoke the function to add our rpc data onto our protoStorage object - protoStorage.descriptorDefinition = findNestedService(protoStorage.descriptor) - - // Store the services from the current .proto file - const serviceArr = []; - - for (const [serviceName, serviceDef] of Object.entries( - protoStorage.descriptorDefinition - )) { - - if (typeof serviceDef === "function") { - // here a service is defined. - const serviceObj = {}; - serviceObj.packageName = protoStorage.packageName; - serviceObj.name = serviceName; - serviceObj.rpcs = []; - serviceObj.messages = []; - - for (const [requestName, requestDef] of Object.entries( - serviceDef.service - )) { - // console.log('request name: ', requestName) - const streamingReq = requestDef.requestStream; - const streamingRes = requestDef.responseStream; - - let stream = "UNARY"; - if (streamingReq) stream = "CLIENT STREAM"; - if (streamingRes) stream = "SERVER STREAM"; - if (streamingReq && streamingRes) stream = "BIDIRECTIONAL"; - const messageNameReq = requestDef.requestType.type.name; - const messageNameRes = requestDef.responseType.type.name; - serviceObj.rpcs.push({ - name: requestName, - type: stream, - req: messageNameReq, - res: messageNameRes, - }); - - // create object with proto info that is formatted for interaction with Swell frontend - let draftObj; - requestDef.requestType.type.field.forEach((msgObj) => { - const mName = msgObj.name; - // bool will track if the message is a nested type - let bool = false; - if (msgObj.type === "TYPE_MESSAGE") bool = true; - if (!draftObj) { - draftObj = { - name: messageNameReq, - def: {}, - }; - } - draftObj.def[mName] = {}; - draftObj.def[mName].type = msgObj.type; - draftObj.def[mName].nested = bool; - draftObj.def[mName].dependent = msgObj.typeName; - - // Frontend expects a message object in the following format - // { - // name: messageNameReq, - // def: { - // [mName]: { - // type:msgObj.type, - // nested: bool, - // dependent: msgObj.typeName} - // } - // } - }); - serviceObj.messages.push(draftObj); - - // not using the details of the response object (requestDef.responseType) since user will run - // their own server - } - serviceArr.push(serviceObj); - } - } - // console.log('service array length - is this ever more than 1?', serviceArr.length) - protoStorage.serviceArr = serviceArr; - - return protoStorage || "Error: please enter valid .proto file"; -} - -module.exports = protoParserFunc; +// this file is being required into main. this function, protoParserFunc, should only be called inside main. + +const fs = require('fs'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const path = require('path'); + +async function protoParserFunc(protoBodyData) { + // define storage for .proto parsed content + const protoStorage = {}; + + // store the original .proto content in the storage before parsing + protoStorage.protoMaster = protoBodyData; + + // make unique protoID for file we are saving + let protoID = Math.floor(Math.random() * 10000); + + // if file path for that ID already exists, increment the ID + try { + if (!fs.existsSync(path.join(process.resourcesPath, '/protos/'))) { + fs.mkdirSync(path.join(process.resourcesPath, '/protos/')); + } + } catch (err) { + console.error(err); + } + + try { + while ( + fs.existsSync( + path.join(process.resourcesPath, `/protos/${protoID}.proto`) + ) + ) { + // if file name exists try incrementing by 1 + protoID += 1; + } + } catch (err) { + console.error(err); + } + + // write to saveProto file for interaction with the server + fs.writeFileSync( + path.join(process.resourcesPath, `/protos/${protoID}.proto`), + protoBodyData, + 'utf-8' + ); + + // define the modular client for testing + // declare path variable of imported proto file + const PROTO_PATH = path.join( + process.resourcesPath, + `/protos/${protoID}.proto` + ); + // store the proto Path on proto storage object + protoStorage.protoPath = PROTO_PATH; + + // create gRPC package options + const protoOptionsObj = { + keepCase: true, + enums: String, + longs: String, + defaults: true, + oneofs: true, + }; + + // create gRPC package definition w/ protoLoader library + protoStorage.packageDefinition = protoLoader.loadSync( + PROTO_PATH, + protoOptionsObj + ); + + // create descriptor from package packageDefinition + // descriptor --> gRPC uses the Protobuf .proto file format to define your messages, services and some aspects of the code generation. + protoStorage.descriptor = grpc.loadPackageDefinition( + protoStorage.packageDefinition + ); + protoStorage.packageName = Object.keys(protoStorage.descriptor)[0]; + + // a recursive helper function to find our service definition. if it's nested, we need to find the actual rpc data + const findNestedService = (obj) => { + if (Object.values(obj).length > 1) return obj; // otherwise... + return findNestedService(Object.values(obj)[0]); + }; + // invoke the function to add our rpc data onto our protoStorage object + protoStorage.descriptorDefinition = findNestedService( + protoStorage.descriptor + ); + + // Store the services from the current .proto file + const serviceArr = []; + + for (const [serviceName, serviceDef] of Object.entries( + protoStorage.descriptorDefinition + )) { + if (typeof serviceDef === 'function') { + // here a service is defined. + const serviceObj = {}; + serviceObj.packageName = protoStorage.packageName; + serviceObj.name = serviceName; + serviceObj.rpcs = []; + serviceObj.messages = []; + + for (const [requestName, requestDef] of Object.entries( + serviceDef.service + )) { + // console.log('request name: ', requestName) + const streamingReq = requestDef.requestStream; + const streamingRes = requestDef.responseStream; + + let stream = 'UNARY'; + if (streamingReq) stream = 'CLIENT STREAM'; + if (streamingRes) stream = 'SERVER STREAM'; + if (streamingReq && streamingRes) stream = 'BIDIRECTIONAL'; + const messageNameReq = requestDef.requestType.type.name; + const messageNameRes = requestDef.responseType.type.name; + serviceObj.rpcs.push({ + name: requestName, + type: stream, + req: messageNameReq, + res: messageNameRes, + }); + + // create object with proto info that is formatted for interaction with Swell frontend + let draftObj; + requestDef.requestType.type.field.forEach((msgObj) => { + const mName = msgObj.name; + // bool will track if the message is a nested type + let bool = false; + if (msgObj.type === 'TYPE_MESSAGE') bool = true; + if (!draftObj) { + draftObj = { + name: messageNameReq, + def: {}, + }; + } + draftObj.def[mName] = {}; + draftObj.def[mName].type = msgObj.type; + draftObj.def[mName].nested = bool; + draftObj.def[mName].dependent = msgObj.typeName; + + // Frontend expects a message object in the following format + // { + // name: messageNameReq, + // def: { + // [mName]: { + // type:msgObj.type, + // nested: bool, + // dependent: msgObj.typeName} + // } + // } + }); + serviceObj.messages.push(draftObj); + + // not using the details of the response object (requestDef.responseType) since user will run + // their own server + } + serviceArr.push(serviceObj); + } + } + // console.log('service array length - is this ever more than 1?', serviceArr.length) + protoStorage.serviceArr = serviceArr; + + return protoStorage || 'Error: please enter valid .proto file'; +} + +module.exports = protoParserFunc; diff --git a/menu/mainMenu.js b/menu/mainMenu.js index 11e9d0166..a3e9657b5 100644 --- a/menu/mainMenu.js +++ b/menu/mainMenu.js @@ -1,198 +1,198 @@ -const { Menu } = require("electron"); -const electron = require("electron"); - -const app = electron.app; -// -------------------------------------------------------------------------------------------------- -// Here we are creating an array of menu tabs. Each menu tab will have its own list items(aka a sub-menu). -// This array called template will be our default menu set up -// -------------------------------------------------------------------------------------------------- -const template = [ - { - label: "Edit", - submenu: [ - { - role: "undo", - }, - { - role: "redo", - }, - { - type: "separator", // A dividing line between menu items - }, - { - role: "cut", - }, - { - role: "copy", - }, - { - role: "paste", - }, - { - role: "pasteandmatchstyle", - }, - { - role: "delete", - }, - { - role: "selectall", - }, - ], - }, - { - label: "View", - submenu: [ - { - label: "Reload", - accelerator: "CmdOrCtrl+R", //keyboard shortcut that will reload the current window - click(item, focusedWindow) { - if (focusedWindow) focusedWindow.reload(); - }, - }, - //THIS ENABLES DEVELOPER TOOLS IN THE BUILT APPLICATION - { - label: "Toggle Developer Tools", - visible: process.env.NODE_ENV === "development", - accelerator: - process.platform === "darwin" ? "Alt+Command+I" : "Ctrl+Shift+I", // another keyboard shortcut that will be conditionally assigned depending on whether or not user is on macOS - click(item, focusedWindow) { - if (focusedWindow) focusedWindow.webContents.toggleDevTools(); - }, - }, - { - type: "separator", - }, - { - role: "resetzoom", - }, - { - role: "zoomin", - }, - { - role: "zoomout", - }, - { - type: "separator", - }, - { - role: "togglefullscreen", - }, - ], - }, - { - role: "window", - submenu: [ - { - role: "minimize", - }, - { - role: "close", - }, - ], - }, - { - role: "help", - submenu: [ - { - label: "Learn More", - click() { - electron.shell.openExternal("http://electron.atom.io"); - }, - }, - ], - }, -]; -// -------------------------------------------------------------------------------------------------- - -// -------------------------------------------------------------------------------------------------- -// If the user is on mac create an extra menu tab that will be labeled as the name of your app -// This new tab will have submenu features that windows and linux users will not have access to -// -------------------------------------------------------------------------------------------------- -if (process.platform === "darwin") { - // if user is on mac... - const name = app.name; - template.unshift({ - // add on these new menu items - label: name, - submenu: [ - { - role: "about", - }, - { - type: "separator", - }, - { - role: "services", - submenu: [], - }, - { - type: "separator", - }, - { - role: "hide", - }, - { - role: "hideothers", - }, - { - role: "unhide", - }, - { - type: "separator", - }, - { - role: "quit", - }, - ], - }); - // template[1] refers to the Edit menu. - template[1].submenu.push( - // If user is on macOS also provide speech based submenu items in addition to the edit menu's other submenu items that were set earlier - { - type: "separator", - }, - { - label: "Speech", - submenu: [ - { - role: "startspeaking", - }, - { - role: "stopspeaking", - }, - ], - } - ); - //template[3] refers to the Window menu. - template[3].submenu = [ - // if user is on macOS replace the Window menu that we created earlier with the submenu below - { - label: "Close", - accelerator: "CmdOrCtrl+W", - role: "close", - }, - { - label: "Minimize", - accelerator: "CmdOrCtrl+M", - role: "minimize", - }, - { - label: "Zoom", - role: "zoom", - }, - { - type: "separator", - }, - { - label: "Bring All to Front", - role: "front", - }, - ]; -} -// create our menu with the Menu module imported from electron, -// use its built in method buildFromTemplate -// and pass in the template we've just created -const menu = Menu.buildFromTemplate(template); - -// append our newly created menu to our app -Menu.setApplicationMenu(menu); +const { Menu } = require('electron'); +const electron = require('electron'); + +const { app } = electron; +// -------------------------------------------------------------------------------------------------- +// Here we are creating an array of menu tabs. Each menu tab will have its own list items(aka a sub-menu). +// This array called template will be our default menu set up +// -------------------------------------------------------------------------------------------------- +const template = [ + { + label: 'Edit', + submenu: [ + { + role: 'undo', + }, + { + role: 'redo', + }, + { + type: 'separator', // A dividing line between menu items + }, + { + role: 'cut', + }, + { + role: 'copy', + }, + { + role: 'paste', + }, + { + role: 'pasteandmatchstyle', + }, + { + role: 'delete', + }, + { + role: 'selectall', + }, + ], + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', // keyboard shortcut that will reload the current window + click(item, focusedWindow) { + if (focusedWindow) focusedWindow.reload(); + }, + }, + // THIS ENABLES DEVELOPER TOOLS IN THE BUILT APPLICATION + { + label: 'Toggle Developer Tools', + visible: process.env.NODE_ENV === 'development', + accelerator: + process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', // another keyboard shortcut that will be conditionally assigned depending on whether or not user is on macOS + click(item, focusedWindow) { + if (focusedWindow) focusedWindow.webContents.toggleDevTools(); + }, + }, + { + type: 'separator', + }, + { + role: 'resetzoom', + }, + { + role: 'zoomin', + }, + { + role: 'zoomout', + }, + { + type: 'separator', + }, + { + role: 'togglefullscreen', + }, + ], + }, + { + role: 'window', + submenu: [ + { + role: 'minimize', + }, + { + role: 'close', + }, + ], + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click() { + electron.shell.openExternal('http://electron.atom.io'); + }, + }, + ], + }, +]; +// -------------------------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------------------------- +// If the user is on mac create an extra menu tab that will be labeled as the name of your app +// This new tab will have submenu features that windows and linux users will not have access to +// -------------------------------------------------------------------------------------------------- +if (process.platform === 'darwin') { + // if user is on mac... + const { name } = app; + template.unshift({ + // add on these new menu items + label: name, + submenu: [ + { + role: 'about', + }, + { + type: 'separator', + }, + { + role: 'services', + submenu: [], + }, + { + type: 'separator', + }, + { + role: 'hide', + }, + { + role: 'hideothers', + }, + { + role: 'unhide', + }, + { + type: 'separator', + }, + { + role: 'quit', + }, + ], + }); + // template[1] refers to the Edit menu. + template[1].submenu.push( + // If user is on macOS also provide speech based submenu items in addition to the edit menu's other submenu items that were set earlier + { + type: 'separator', + }, + { + label: 'Speech', + submenu: [ + { + role: 'startspeaking', + }, + { + role: 'stopspeaking', + }, + ], + } + ); + // template[3] refers to the Window menu. + template[3].submenu = [ + // if user is on macOS replace the Window menu that we created earlier with the submenu below + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close', + }, + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize', + }, + { + label: 'Zoom', + role: 'zoom', + }, + { + type: 'separator', + }, + { + label: 'Bring All to Front', + role: 'front', + }, + ]; +} +// create our menu with the Menu module imported from electron, +// use its built in method buildFromTemplate +// and pass in the template we've just created +const menu = Menu.buildFromTemplate(template); + +// append our newly created menu to our app +Menu.setApplicationMenu(menu); diff --git a/package.json b/package.json index 1ffcac315..4daa2fb21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "Swell", - "version": "0.9.0", + "name": "swell", + "version": "0.10.0", "description": "Swell", "main": "main.js", "repository": "https://github.com/open-source-labs/Swell", @@ -12,8 +12,12 @@ "server-http": "node ./test/httpServer.js", "server-http2": "node ./test/HTTP2_server.js", "server-websocket": "node ./test/websocketServer.js", + "server-webrtc": "node ./test/webrtcWSServer.js", "test-jest": "jest", "test-mocha": "webpack --mode=production --config ./webpack.production.js && cross-env process.env.NODE_ENV=test mocha --exit", + "format": "prettier --write \"**/*.+(js|jsx| tsx| json|css|md)\"", + "lint": "eslint .", + "lint:fix": "eslint --fix . ", "build": "webpack --mode=production --config ./webpack.production.js", "dev": "webpack-dev-server --mode=development --config ./webpack.development.js", "prod": "webpack --mode=production --config ./webpack.production.js && electron --noDevServer .", @@ -25,188 +29,6 @@ "gh-publish": "electron-builder build -mwl -p always", "check-types": "tsc" }, - "author": { - "name": "Swell", - "email": "swell@getswell.io", - "url": "http://www.getswell.io" - }, - "contributors": [ - { - "name": "Kyle Combs", - "email": "combskyle@gmail.com", - "url": "https://github.com/texpatnyc" - }, - { - "name": "Jason Ou", - "email": "jasonou122894@gmail.com", - "url": "https://github.com/jasonou1994" - }, - { - "name": "Anthony Terruso", - "email": "aterruso@gmail.com", - "url": "https://github.com/discrete-projects" - }, - { - "name": "Brandon Marrero", - "email": "brandon6190@outlook.com", - "url": "https://github.com/brandon6190" - }, - { - "name": "Abby Chao", - "email": "abigail.chao@gmail.com", - "url": "https://github.com/abbychao" - }, - { - "name": "Kwadwo Asamoah", - "email": "addoasa94@gmail.com", - "url": "https://github.com/addoasa" - }, - { - "name": "Kajol Thapa", - "email": "kajol.thapa.dev@gmail.com", - "url": "https://github.com/KajolThapa" - }, - { - "name": "Amanda Flink", - "email": "avflinkette@gmail.com", - "url": "https://github.com/aflinky" - }, - { - "name": "Anthony Toreson", - "email": "anthony.toreson@gmail.com", - "url": "https://github.com/atoreson" - }, - { - "name": "Billy Tran", - "email": "billy.tran61@gmail.com", - "url": "https://github.com/btctrl" - }, - { - "name": "Paul Rhee", - "email": "youjun27@gmail.com", - "url": "https://github.com/prheee" - }, - { - "name": "Sam Parsons", - "email": "samparsons269@gmail.com", - "url": "https://github.com/sam-parsons" - }, - { - "name": "Dan Stein", - "email": "50937807+danst3in@users.noreply.github.com", - "url": "https://github.com/danst3in" - }, - { - "name": "Evan Grobar", - "email": "egrobar@gmail.com", - "url": "https://github.com/egrobar" - }, - { - "name": "Nancy Dao", - "email": "nancyddao@gmail.com", - "url": "https://github.com/nancyddao" - }, - { - "name": "Yoon Choi", - "email": "choi.yoon333@gmail.com", - "url": "https://github.com/cyoonique" - }, - { - "name": "Amruth Uppaluri", - "email": "amruth@uppaluri.org", - "url": "https://github.com/amuuth" - }, - { - "name": "Hideaki Aomori", - "email": "https://www.linkedin.com/in/hideaki-aomori", - "url": "http://github.com/h15200" - }, - { - "name": "Matt Gin", - "email": "mgin1013@gmail.com", - "url": "https://github.com/ChunsonHoag" - }, - { - "name": "Nick Healy", - "email": "nickrhealy@gmail.com", - "url": "http://github.com/nickhealy" - }, - { - "name": "Grace Spletzer", - "email": "gracespletzer05@gmail.com", - "url": "https://github.com/gspletzer" - }, - { - "name": "Stephanie Wood", - "email": "wood.steph@gmail.com", - "url": "https://github.com/StephWood" - }, - { - "name": "Wyatt Bell", - "email": "www.linkedin.com/in/wyatt-bell1", - "url": "https://github.com/wcbell51" - }, - { - "name": "Grace Kim", - "email": "https://www.linkedin.com/in/gracekiim/", - "url": "https://github.com/gracekiim" - }, - { - "name": "John Madrigal", - "email": "www.linkedin.com/in/john-r-madrigal", - "url": "https://github.com/johnmadrigal " - }, - { - "name": "Michael Miller", - "email": "https://www.linkedin.com/in/migueljmiller/", - "url": "https://github.com/mjmiguel" - }, - { - "name": "Alex Sanhueza", - "email": "https://www.linkedin.com/in/alex-sanhueza/", - "url": "https://github.com/alexsanhueza" - }, - { - "name": "Gary Slootskiy", - "email": "https://www.linkedin.com/in/garyslootskiy/", - "url": "https://github.com/garyslootskiy" - }, - { - "name": "Robin Yoong", - "email": "https://www.linkedin.com/in/robinyoong/", - "url": "https://github.com/robinyoong" - }, - { - "name": "Nathaniel Adams", - "email": "https://www.linkedin.com/in/adamsnathaniel/", - "url": "https://github.com/nathanielBadams" - }, - { - "name": "Sam Haar", - "email": "https://www.linkedin.com/in/samhaar/", - "url": "https://github.com/samhaar" - }, - { - "name": "Edward Cho", - "email": "https://www.linkedin.com/in/edwardcho1231/", - "url": "https://github.com/edwardcho1231" - }, - { - "name": "Miguel Gonzalez", - "email": "https://www.linkedin.com/in/miguel-gonzalez96/", - "url": "https://github.com/MigGonzalez" - }, - { - "name": "Jason Liggayu", - "email": "https://www.linkedin.com/in/jasonliggayu/", - "url": "https://github.com/jasonligg" - }, - { - "name": "Warren Tait", - "email": "https://www.linkedin.com/in/warrenhtait/", - "url": "https://github.com/whtait" - } - ], "build": { "npmRebuild": false, "productName": "Swell", @@ -223,6 +45,7 @@ "package.json", "main.js", "main_process/protoParser.js", + "main_process/openapiParser.js", "main_process/main_grpcController.js", "main_process/main_graphqlController.js", "main_process/main_httpController.js", @@ -303,6 +126,7 @@ "chart.js": "^2.9.3", "classnames": "^2.2.6", "codemirror": "^5.58.2", + "yamljs": "^0.3.0", "codemirror-graphql": "^0.12.1", "concurrently": "^5.2.0", "date-fns": "^1.29.0", @@ -318,20 +142,22 @@ "express-sse": "^0.5.3", "google-auth-library": "^5.10.1", "graphql": "^14.6.0", + "graphql-language-service-interface": "2.8.2", + "graphql-language-service-parser": "1.9.0", "graphql-subscriptions": "^1.1.0", "graphql-tag": "^2.10.3", "graphql-tools": "^6.1.0", - "grpc": "^1.24.3", "highland": "^2.13.5", - "http": "0.0.1-security", "identity-obj-proxy": "^3.0.0", + "js-beautify": "^1.14.0", "mali": "^0.21.0", "material-ui-dropzone": "^3.5.0", "mongoose": "^5.9.29", "node-fetch": "^2.6.0", - "node-pre-gyp": "0.6.x", + "node-pre-gyp": "^0.6.39", "node-sass": "^4.14.1", "nodemon": "^1.19.4", + "npm-http-server": "^4.3.0", "path": "^0.12.7", "prop-types": "^15.6.2", "react": "^16.13.1", @@ -358,9 +184,7 @@ "vm2": "^3.9.2", "webpack-merge": "^4.2.2", "websocket": "^1.0.31", - "ws": "^6.1.2", - "graphql-language-service-interface": "2.8.2", - "graphql-language-service-parser": "1.9.0" + "ws": "^6.1.2" }, "devDependencies": { "@babel/core": "^7.10.3", @@ -401,27 +225,29 @@ "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "enzyme-to-json": "^3.5.0", - "eslint": "^5.9.0", - "eslint-config-airbnb": "^17.1.0", - "eslint-config-prettier": "^6.11.0", + "eslint": "^7.30.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-config-prettier": "^8.3.0", "eslint-import-resolver-typescript": "^2.0.0", - "eslint-plugin-import": "^2.21.2", - "eslint-plugin-jest": "^23.17.1", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.20.0", - "eslint-plugin-react-hooks": "^4.0.4", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jest": "^24.3.6", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^1.7.0", "fake-indexeddb": "^3.0.2", "file-loader": "^2.0.0", "html-webpack-plugin": "^4.3.0", "jest": "^24.9.0", "mini-css-extract-plugin": "^0.9.0", - "mocha": "*", + "mocha": "^9.0.3", "postcss-cssnext": "^3.1.0", "postcss-import": "^12.0.1", "postcss-loader": "^3.0.0", "postcss-nested": "^4.2.1", "postcss-pxtorem": "^4.0.1", "prettier": "^2.0.5", + "prettier-eslint": "^13.0.0", "react-devtools-electron": "^4.7.0", "react-test-renderer": "^16.13.1", "sass-loader": "^7.3.1", @@ -436,5 +262,207 @@ "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0", "webpack-node-externals": "^1.7.2" - } + }, + "author": { + "name": "Swell", + "email": "swell@getswell.io", + "url": "http://www.getswell.io" + }, + "contributors": [ + { + "name": "Kyle Combs", + "email": "combskyle@gmail.com", + "url": "https://github.com/texpatnyc" + }, + { + "name": "Jason Ou", + "email": "jasonou122894@gmail.com", + "url": "https://github.com/jasonou1994" + }, + { + "name": "Anthony Terruso", + "email": "aterruso@gmail.com", + "url": "https://github.com/discrete-projects" + }, + { + "name": "Brandon Marrero", + "email": "brandon6190@outlook.com", + "url": "https://github.com/brandon6190" + }, + { + "name": "Abby Chao", + "email": "abigail.chao@gmail.com", + "url": "https://github.com/abbychao" + }, + { + "name": "Kwadwo Asamoah", + "email": "addoasa94@gmail.com", + "url": "https://github.com/addoasa" + }, + { + "name": "Kajol Thapa", + "email": "kajol.thapa.dev@gmail.com", + "url": "https://github.com/KajolThapa" + }, + { + "name": "Amanda Flink", + "email": "avflinkette@gmail.com", + "url": "https://github.com/aflinky" + }, + { + "name": "Anthony Toreson", + "email": "anthony.toreson@gmail.com", + "url": "https://github.com/atoreson" + }, + { + "name": "Billy Tran", + "email": "billy.tran61@gmail.com", + "url": "https://github.com/btctrl" + }, + { + "name": "Paul Rhee", + "email": "youjun27@gmail.com", + "url": "https://github.com/prheee" + }, + { + "name": "Sam Parsons", + "email": "samparsons269@gmail.com", + "url": "https://github.com/sam-parsons" + }, + { + "name": "Dan Stein", + "email": "50937807+danst3in@users.noreply.github.com", + "url": "https://github.com/danst3in" + }, + { + "name": "Evan Grobar", + "email": "egrobar@gmail.com", + "url": "https://github.com/egrobar" + }, + { + "name": "Nancy Dao", + "email": "nancyddao@gmail.com", + "url": "https://github.com/nancyddao" + }, + { + "name": "Yoon Choi", + "email": "choi.yoon333@gmail.com", + "url": "https://github.com/cyoonique" + }, + { + "name": "Amruth Uppaluri", + "email": "amruth@uppaluri.org", + "url": "https://github.com/amuuth" + }, + { + "name": "Hideaki Aomori", + "email": "https://www.linkedin.com/in/hideaki-aomori", + "url": "http://github.com/h15200" + }, + { + "name": "Matt Gin", + "email": "mgin1013@gmail.com", + "url": "https://github.com/ChunsonHoag" + }, + { + "name": "Nick Healy", + "email": "nickrhealy@gmail.com", + "url": "http://github.com/nickhealy" + }, + { + "name": "Grace Spletzer", + "email": "gracespletzer05@gmail.com", + "url": "https://github.com/gspletzer" + }, + { + "name": "Stephanie Wood", + "email": "wood.steph@gmail.com", + "url": "https://github.com/StephWood" + }, + { + "name": "Wyatt Bell", + "email": "www.linkedin.com/in/wyatt-bell1", + "url": "https://github.com/wcbell51" + }, + { + "name": "Grace Kim", + "email": "https://www.linkedin.com/in/gracekiim/", + "url": "https://github.com/gracekiim" + }, + { + "name": "John Madrigal", + "email": "www.linkedin.com/in/john-r-madrigal", + "url": "https://github.com/johnmadrigal " + }, + { + "name": "Michael Miller", + "email": "https://www.linkedin.com/in/migueljmiller/", + "url": "https://github.com/mjmiguel" + }, + { + "name": "Alex Sanhueza", + "email": "https://www.linkedin.com/in/alex-sanhueza/", + "url": "https://github.com/alexsanhueza" + }, + { + "name": "Gary Slootskiy", + "email": "https://www.linkedin.com/in/garyslootskiy/", + "url": "https://github.com/garyslootskiy" + }, + { + "name": "Robin Yoong", + "email": "https://www.linkedin.com/in/robinyoong/", + "url": "https://github.com/robinyoong" + }, + { + "name": "Nathaniel Adams", + "email": "https://www.linkedin.com/in/adamsnathaniel/", + "url": "https://github.com/nathanielBadams" + }, + { + "name": "Sam Haar", + "email": "https://www.linkedin.com/in/samhaar/", + "url": "https://github.com/samhaar" + }, + { + "name": "Edward Cho", + "email": "https://www.linkedin.com/in/edwardcho1231/", + "url": "https://github.com/edwardcho1231" + }, + { + "name": "Miguel Gonzalez", + "email": "https://www.linkedin.com/in/miguel-gonzalez96/", + "url": "https://github.com/MigGonzalez" + }, + { + "name": "Jason Liggayu", + "email": "https://www.linkedin.com/in/jasonliggayu/", + "url": "https://github.com/jasonligg" + }, + { + "name": "Warren Tait", + "email": "https://www.linkedin.com/in/warrenhtait/", + "url": "https://github.com/whtait" + }, + { + "name": "Ted Craig", + "email": "https://www.linkedin.com/in/mr-ted-craig/", + "url": "https://github.com/tedcraig" + }, + { + "name": "Colin Gibson", + "email": "https://www.linkedin.com/in/colin--gibson/", + "url": "https://github.com/cgefx" + }, + { + "name": "John Jongsun Suh", + "email": "https://www.linkedin.com/in/john-jongsun-suh/", + "url": "https://github.com/MajorLift" + }, + { + "name": "Anthony Wong", + "email": "https://www.linkedin.com/in/aw-anthony/", + "url": "https://github.com/awong428" + } + ] } diff --git a/preload.js b/preload.js index edfabec49..d707a2dbb 100644 --- a/preload.js +++ b/preload.js @@ -1,64 +1,69 @@ -const { ipcRenderer, contextBridge } = require("electron"); - -const apiObj = { - send: (channel, ...data) => { - // allowlist channels SENDING to Main - const allowedChannels = [ - "check-for-update", - "confirm-clear-history", - "export-collection", - "fatalError", - "import-collection", - "import-proto", - "open-http", - "close-http", - "open-gql", - "open-grpc", - "protoParserFunc-request", - "quit-and-install", - "uncaughtException", - "introspect", - "open-ws", - "send-ws", - "close-ws", - "exportChatLog" - ]; - if (allowedChannels.includes(channel)) { - ipcRenderer.send(channel, ...data); - } - }, - receive: (channel, cb) => { - // allowlist channels - const allowedChannels = [ - "add-collection", - "clear-history-response", - "message", - "proto-info", - "protoParserFunc-return", - "reply-gql", - "reqResUpdate", - "introspect-reply", - "update-connectionArray", - ]; - if (allowedChannels.includes(channel)) { - ipcRenderer.on(channel, (event, ...args) => cb(...args)); - } - }, - removeAllListeners: (channel, cb) => { - // allowlist channels - const allowedChannels = ["reqResUpdate", "reply-gql"]; - if (allowedChannels.includes(channel)) { - ipcRenderer.removeAllListeners(channel, (event, ...args) => cb(...args)); - } - }, -}; - -// this is because we need to have context isolation to be false for spectron tests to run, but context bridge only runs if context isolation is true -// basically we are assigning certain node functionality (require, ipcRenderer) to the window object in an UN-isolated context only for testing -// security is reduced for testing, but remains sturdy otherwise -if (process.env.NODE_ENV === "test") { - window.electronRequire = require; - window.api = apiObj; -} else { - contextBridge.exposeInMainWorld("api", apiObj); -} +const { ipcRenderer, contextBridge } = require('electron'); + +const apiObj = { + send: (channel, ...data) => { + // allow list channels SENDING to Main + const allowedChannels = [ + 'check-for-update', + 'confirm-clear-history', + 'export-collection', + 'fatalError', + 'import-collection', + 'import-proto', + 'import-openapi', + 'open-http', + 'close-http', + 'open-gql', + 'open-grpc', + 'protoParserFunc-request', + 'openapiParserFunc-request', + 'quit-and-install', + 'uncaughtException', + 'introspect', + 'open-ws', + 'send-ws', + 'close-ws', + 'open-openapi', + 'exportChatLog', + ]; + if (allowedChannels.includes(channel)) { + ipcRenderer.send(channel, ...data); + } + }, + receive: (channel, cb) => { + // allow list channels + const allowedChannels = [ + 'add-collection', + 'clear-history-response', + 'message', + 'openapi-info', + 'proto-info', + 'protoParserFunc-return', + 'openapiParserFunc-return', + 'reply-gql', + 'reqResUpdate', + 'introspect-reply', + 'update-connectionArray', + ]; + if (allowedChannels.includes(channel)) { + ipcRenderer.on(channel, (event, ...args) => cb(...args)); + } + }, + removeAllListeners: (channel, cb) => { + // allow list channels + const allowedChannels = ['reqResUpdate', 'reply-gql']; + if (allowedChannels.includes(channel)) { + ipcRenderer.removeAllListeners(channel, (event, ...args) => cb(...args)); + } + }, +}; + +// this is because we need to have context isolation to be false for spectron tests to run, but context bridge only runs if context isolation is true +// basically we are assigning certain node functionality (require, ipcRenderer) to the window object in an UN-isolated context only for testing +// security is reduced for testing, but remains sturdy otherwise +if (process.env.NODE_ENV === 'test') { + window.electronRequire = require; + window.api = apiObj; +} else { + contextBridge.exposeInMainWorld('api', apiObj); +} diff --git a/src/assets/style/colors.scss b/src/assets/style/colors.scss index 3a9f86751..b07409442 100644 --- a/src/assets/style/colors.scss +++ b/src/assets/style/colors.scss @@ -8,6 +8,8 @@ $rest: #6843ff; $graphql: #de33a6; $grpc: #6cacc1; $websockets: #f47d31; +$webrtc: #4285f4; +$openapi: #94c73d; $neutral-100: #ffffff; $neutral-200: #f4f5f7; @@ -28,12 +30,28 @@ $text: $neutral-500; // $dark: $grey-darker // CODE MIRROR COLORS -.cm-s-neo .cm-comment { color: $neutral-400 !important; } -.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color: #A626A4 !important; } -.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:$primary-200 !important; } -.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color: #a57b25 !important; } -.cm-s-neo .cm-string { color: $primary-500 !important; } -.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color: #06a568 !important; } +.cm-s-neo .cm-comment { + color: $neutral-400 !important; +} +.cm-s-neo .cm-keyword, +.cm-s-neo .cm-property { + color: #a626a4 !important; +} +.cm-s-neo .cm-atom, +.cm-s-neo .cm-number { + color: $primary-200 !important; +} +.cm-s-neo .cm-node, +.cm-s-neo .cm-tag { + color: #a57b25 !important; +} +.cm-s-neo .cm-string { + color: $primary-500 !important; +} +.cm-s-neo .cm-variable, +.cm-s-neo .cm-qualifier { + color: #06a568 !important; +} // COLOR SCHEMES .is-primary-100 { @@ -66,6 +84,16 @@ $text: $neutral-500; background-color: $grpc !important; } +.is-webrtc { + color: $neutral-200 !important; + background-color: $webrtc !important; +} + +.is-openapi { + color: $neutral-200 !important; + background-color: $openapi !important; +} + .is-ws { color: $neutral-200 !important; background-color: $websockets !important; @@ -83,9 +111,16 @@ $text: $neutral-500; color: $grpc !important; } +.is-openapi-color { + color: $openapi !important; +} + .is-ws-color { color: $websockets !important; } +.is-webrtc-color { + color: $webrtc !important; +} .is-rest-border { border-color: $rest !important; @@ -95,6 +130,10 @@ $text: $neutral-500; border-color: $graphql !important; } +.is-openapi-border { + border-color: $openapi !important; +} + .is-grpc-border { border-color: $grpc !important; } @@ -103,6 +142,10 @@ $text: $neutral-500; border-color: $websockets !important; } +.is-webrtc-border { + border-color: $webrtc !important; +} + .is-neutral-300 { color: $neutral-600 !important; background-color: $neutral-300 !important; diff --git a/src/assets/style/infinite.css b/src/assets/style/infinite.css index fa150d0ac..e3ded426d 100644 --- a/src/assets/style/infinite.css +++ b/src/assets/style/infinite.css @@ -1,66 +1,88 @@ -/* Make the element pulse (grow large and small slowly) */ -/* Usage - .myElement { - animation: pulsate 1s ease-out; - animation-iteration-count: infinite; - opacity: 1; - } -*/ -@-webkit-keyframes pulsate { - 0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;} - 50% {opacity: 1.0;} - 100% {-webkit-transform: scale(1.2, 1.2); opacity: 0.0;} -} - -/* Make the element's opacity pulse*/ -/* Usage - .myElement { - animation: opacityPulse 1s ease-out; - animation-iteration-count: infinite; - opacity: 0; - } -*/ -@-webkit-keyframes opacityPulse { - 0% {opacity: 0.0;} - 50% {opacity: 1.0;} - 100% {opacity: 0.0;} -} - -/* Make the element's background pulse. I call this alertPulse because it is red. You can call it something more generic. */ -/* Usage - .myElement { - animation: alertPulse 1s ease-out; - animation-iteration-count: infinite; - opacity: 1; - } -*/ -@-webkit-keyframes alertPulse { - 0% {background-color: #9A2727; opacity: 1;} - 50% {opacity: red; opacity: 0.75; } - 100% {opacity: #9A2727; opacity: 1;} -} - - -/* Make the element rotate infinitely. */ -/* -Usage - .myElement { - animation: rotating 3s linear infinite; - } -*/ -@keyframes rotating { - from { - -ms-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - -o-transform: rotate(0deg); - transform: rotate(0deg); - } - to { - -ms-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -webkit-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } -} \ No newline at end of file +/* Make the element pulse (grow large and small slowly) */ +/* Usage + .myElement { + animation: pulsate 1s ease-out; + animation-iteration-count: infinite; + opacity: 1; + } +*/ +@-webkit-keyframes pulsate { + 0% { + -webkit-transform: scale(0.1, 0.1); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + -webkit-transform: scale(1.2, 1.2); + opacity: 0; + } +} + +/* Make the element's opacity pulse*/ +/* Usage + .myElement { + animation: opacityPulse 1s ease-out; + animation-iteration-count: infinite; + opacity: 0; + } +*/ +@-webkit-keyframes opacityPulse { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +/* Make the element's background pulse. I call this alertPulse because it is red. You can call it something more generic. */ +/* Usage + .myElement { + animation: alertPulse 1s ease-out; + animation-iteration-count: infinite; + opacity: 1; + } +*/ +@-webkit-keyframes alertPulse { + 0% { + background-color: #9a2727; + opacity: 1; + } + 50% { + opacity: red; + opacity: 0.75; + } + 100% { + opacity: #9a2727; + opacity: 1; + } +} + +/* Make the element rotate infinitely. */ +/* +Usage + .myElement { + animation: rotating 3s linear infinite; + } +*/ +@keyframes rotating { + from { + -ms-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -ms-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/src/assets/style/webSocketResponse.scss b/src/assets/style/webSocketResponse.scss index 7be14c984..991ad333c 100644 --- a/src/assets/style/webSocketResponse.scss +++ b/src/assets/style/webSocketResponse.scss @@ -36,6 +36,7 @@ width: 100%; height: 100%; padding-right: 17px; + box-sizing: content-box; } @@ -49,6 +50,7 @@ padding: 4px 8x; color: #fff; text-align: right; + margin-right: 8px; } .websocket_message-server { @@ -62,12 +64,11 @@ align-self: flex-end; } - .animatedmain { position: relative; width: 200px; height: 50px; - background: #F66B61; + background: #f66b61; border-radius: 5px; border: 2px solid white; overflow: hidden; @@ -79,7 +80,7 @@ left: 0; width: 100%; height: 100%; - background: #21D4A4; + background: #21d4a4; } .animatedcontent { @@ -92,6 +93,6 @@ align-items: center; justify-content: center; color: white; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', - 'Helvetica Neue', sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } diff --git a/src/client/actions/actionTypes.js b/src/client/actions/actionTypes.js index 0456279a0..cf1e18357 100644 --- a/src/client/actions/actionTypes.js +++ b/src/client/actions/actionTypes.js @@ -1,47 +1,56 @@ -// BUSINESS LOGIC ACTIONS -export const REQRES_CLEAR = "REQRES_CLEAR"; -export const REQRES_ADD = "REQRES_ADD"; -export const REQRES_DELETE = "REQRES_DELETE"; -export const REQRES_UPDATE = "REQRES_UPDATE"; -export const SCHEDULED_REQRES_UPDATE = "SCHEDULED_REQRES_UPDATE"; -export const SCHEDULED_REQRES_DELETE = "SCHEDULED_REQRES_DELETE" -export const UPDATE_GRAPH = "UPDATE_GRAPH"; - -export const CLEAR_GRAPH = "CLEAR_GRAPH"; -export const CLEAR_ALL_GRAPH = "CLEAR_ALL_GRAPH"; - -export const GET_HISTORY = "GET_HISTORY"; -export const DELETE_HISTORY = "DELETE_HISTORY"; -export const CLEAR_HISTORY = "CLEAR_HISTORY"; - -export const GET_COLLECTIONS = "GET_COLLECTIONS"; -export const DELETE_COLLECTION = "DELETE_COLLECTION"; -export const COLLECTION_TO_REQRES = "COLLECTION_TO_REQRES"; -export const COLLECTION_ADD = "COLLECTION_ADD"; -export const COLLECTION_UPDATE = "COLLECTION_UPDATE"; - -export const SET_COMPOSER_WARNING_MESSAGE = "SET_COMPOSER_WARNING_MESSAGE"; - -export const RESET_COMPOSER_FIELDS = "RESET_COMPOSER_FIELDS"; - -export const SET_NEW_REQUEST_FIELDS = "SET_NEW_REQUEST_FIELDS"; -export const SET_NEW_REQUEST_HEADERS = "SET_NEW_REQUEST_HEADERS"; -export const SET_NEW_REQUEST_STREAMS = "SET_NEW_REQUEST_STREAMS"; -export const SET_NEW_REQUEST_BODY = "SET_NEW_REQUEST_BODY"; -export const SET_NEW_REQUEST_COOKIES = "SET_NEW_REQUEST_COOKIES"; -export const SET_NEW_REQUEST_SSE = "SET_NEW_REQUEST_SSE"; -export const SET_NEW_TEST_CONTENT = "SET_NEW_TEST_CONTENT"; - -export const SET_CURRENT_TAB = "SET_CURRENT_TAB"; - -export const SET_CHECKS_AND_MINIS = "SET_CHECKS_AND_MINIS"; - -export const SET_INTROSPECTION_DATA = "SET_INTROSPECTION_DATA"; - -export const SAVE_CURRENT_RESPONSE_DATA = "SAVE_CURRENT_RESPONSE_DATA"; - -//UI ACTIONS -export const SET_COMPOSER_DISPLAY = "SET_COMPOSER_DISPLAY"; -export const SET_SIDEBAR_ACTIVE_TAB = "SET_SIDEBAR_ACTIVE_TAB"; -export const SET_WORKSPACE_ACTIVE_TAB = "SET_WORKSPACE_ACTIVE_TAB"; -export const SET_RESPONSE_PANE_ACTIVE_TAB = "SET_RESPONSE_PANE_ACTIVE_TAB"; +// BUSINESS LOGIC ACTIONS +export const REQRES_CLEAR = 'REQRES_CLEAR'; +export const REQRES_ADD = 'REQRES_ADD'; +export const REQRES_DELETE = 'REQRES_DELETE'; +export const REQRES_UPDATE = 'REQRES_UPDATE'; +export const SCHEDULED_REQRES_UPDATE = 'SCHEDULED_REQRES_UPDATE'; +export const SCHEDULED_REQRES_DELETE = 'SCHEDULED_REQRES_DELETE'; +export const UPDATE_GRAPH = 'UPDATE_GRAPH'; + +export const CLEAR_GRAPH = 'CLEAR_GRAPH'; +export const CLEAR_ALL_GRAPH = 'CLEAR_ALL_GRAPH'; + +export const GET_HISTORY = 'GET_HISTORY'; +export const DELETE_HISTORY = 'DELETE_HISTORY'; +export const CLEAR_HISTORY = 'CLEAR_HISTORY'; + +export const GET_COLLECTIONS = 'GET_COLLECTIONS'; +export const DELETE_COLLECTION = 'DELETE_COLLECTION'; +export const COLLECTION_TO_REQRES = 'COLLECTION_TO_REQRES'; +export const COLLECTION_ADD = 'COLLECTION_ADD'; +export const COLLECTION_UPDATE = 'COLLECTION_UPDATE'; + +export const SET_COMPOSER_WARNING_MESSAGE = 'SET_COMPOSER_WARNING_MESSAGE'; + +export const RESET_COMPOSER_FIELDS = 'RESET_COMPOSER_FIELDS'; + +export const SET_NEW_REQUEST_FIELDS = 'SET_NEW_REQUEST_FIELDS'; +export const SET_NEW_REQUEST_HEADERS = 'SET_NEW_REQUEST_HEADERS'; +export const SET_NEW_REQUEST_STREAMS = 'SET_NEW_REQUEST_STREAMS'; +export const SET_NEW_REQUEST_BODY = 'SET_NEW_REQUEST_BODY'; +export const SET_NEW_REQUEST_COOKIES = 'SET_NEW_REQUEST_COOKIES'; +export const SET_NEW_REQUEST_SSE = 'SET_NEW_REQUEST_SSE'; +export const SET_NEW_TEST_CONTENT = 'SET_NEW_TEST_CONTENT'; + +export const SET_CURRENT_TAB = 'SET_CURRENT_TAB'; + +export const SET_CHECKS_AND_MINIS = 'SET_CHECKS_AND_MINIS'; + +export const SET_INTROSPECTION_DATA = 'SET_INTROSPECTION_DATA'; + +export const SAVE_CURRENT_RESPONSE_DATA = 'SAVE_CURRENT_RESPONSE_DATA'; + +// UI ACTIONS +export const SET_COMPOSER_DISPLAY = 'SET_COMPOSER_DISPLAY'; +export const SET_SIDEBAR_ACTIVE_TAB = 'SET_SIDEBAR_ACTIVE_TAB'; +export const SET_WORKSPACE_ACTIVE_TAB = 'SET_WORKSPACE_ACTIVE_TAB'; +export const SET_RESPONSE_PANE_ACTIVE_TAB = 'SET_RESPONSE_PANE_ACTIVE_TAB'; + +// OPENAPI ACTIONS +export const SET_NEW_REQUESTS_OPENAPI = 'SET_NEW_REQUESTS_OPENAPI'; +export const SET_OPENAPI_SERVERS_GLOBAL = 'SET_OPENAPI_SERVERS_GLOBAL'; +export const SET_NEW_OPENAPI_SERVERS = 'SET_NEW_OPENAPI_SERVERS'; +export const SET_NEW_OPENAPI_PARAMETER = 'SET_NEW_OPENAPI_PARAMETER'; +export const SET_NEW_OPENAPI_REQUEST_BODY = 'SET_NEW_OPENAPI_REQUEST_BODY'; + +export const QUEUE_OPENAPI_REQUESTS = 'QUEUE_OPENAPI_REQUESTS'; diff --git a/src/client/actions/actions.js b/src/client/actions/actions.js index d0e0aae8c..1935c793a 100644 --- a/src/client/actions/actions.js +++ b/src/client/actions/actions.js @@ -1,172 +1,201 @@ -import * as types from "./actionTypes"; - -// BUSINESS LOGIC ACTIONS - -//action creators -export const getHistory = (history) => ({ - type: types.GET_HISTORY, - payload: history, -}); - -export const deleteFromHistory = (reqRes) => ({ - type: types.DELETE_HISTORY, - payload: reqRes, -}); - -export const clearHistory = () => ({ - type: types.CLEAR_HISTORY, -}); - -export const getCollections = (collections) => ({ - type: types.GET_COLLECTIONS, - payload: collections, -}); - -export const deleteFromCollection = (collection) => ({ - type: types.DELETE_COLLECTION, - payload: collection, -}); - -export const collectionToReqRes = (reqResArray) => ({ - type: types.COLLECTION_TO_REQRES, - payload: reqResArray, -}); - -export const collectionAdd = (collection) => ({ - type: types.COLLECTION_ADD, - payload: collection, -}); - -export const collectionUpdate = (collection) => ({ - type: types.COLLECTION_UPDATE, - payload: collection, -}); - -export const reqResClear = () => ({ - type: types.REQRES_CLEAR, -}); - -export const reqResAdd = (reqRes) => ({ - type: types.REQRES_ADD, - payload: reqRes, -}); - -export const reqResDelete = (reqRes) => ({ - type: types.REQRES_DELETE, - payload: reqRes, -}); - -export const reqResUpdate = (reqRes) => ({ - type: types.REQRES_UPDATE, - payload: reqRes, -}); - -export const scheduledReqResUpdate = (reqRes) => ({ - type: types.SCHEDULED_REQRES_UPDATE, - payload: reqRes, -}); - -export const scheduledReqResDelete = () => ({ - type: types.SCHEDULED_REQRES_DELETE, -}); - -export const updateGraph = (id) => ({ - type: types.UPDATE_GRAPH, - payload: id, -}); - -export const clearGraph = (reqRes) => ({ - type: types.CLEAR_GRAPH, - payload: reqRes, -}); - -export const clearAllGraph = () => ({ - type: types.CLEAR_ALL_GRAPH, -}); - -export const setComposerWarningMessage = (message) => ({ - type: types.SET_COMPOSER_WARNING_MESSAGE, - payload: message, -}); - -export const resetComposerFields = () => ({ - type: types.RESET_COMPOSER_FIELDS, -}); - -export const setNewRequestFields = (requestObj) => ({ - type: types.SET_NEW_REQUEST_FIELDS, - payload: requestObj, -}); - -export const setNewRequestHeaders = (headers) => ({ - type: types.SET_NEW_REQUEST_HEADERS, - payload: headers, -}); - -export const setNewRequestStreams = (streams) => ({ - type: types.SET_NEW_REQUEST_STREAMS, - payload: streams, -}); - -export const setNewRequestBody = (body) => ({ - type: types.SET_NEW_REQUEST_BODY, - payload: body, -}); - -export const setNewTestContent = (content) => ({ - type: types.SET_NEW_TEST_CONTENT, - payload: content, -}); - -export const setNewRequestCookies = (cookies) => ({ - type: types.SET_NEW_REQUEST_COOKIES, - payload: cookies, -}); - -export const setNewRequestSSE = (SSEBool) => ({ - type: types.SET_NEW_REQUEST_SSE, - payload: SSEBool, -}); - -export const setCurrentTab = (tab) => ({ - type: types.SET_CURRENT_TAB, - payload: tab, -}); - -export const setChecksAndMinis = (reqResArray) => ({ - type: types.SET_CHECKS_AND_MINIS, - payload: reqResArray, -}); - -export const setIntrospectionData = (dataObj) => ({ - type: types.SET_INTROSPECTION_DATA, - payload: dataObj, -}); - -export const saveCurrentResponseData = (dataObj, callingFunc) => ({ - type: types.SAVE_CURRENT_RESPONSE_DATA, - payload: dataObj, - callingFunc, -}); - -// UI ACTIONS - -export const setComposerDisplay = (composerDisplay) => ({ - type: types.SET_COMPOSER_DISPLAY, - payload: composerDisplay, -}); - -export const setSidebarActiveTab = (tabName) => ({ - type: types.SET_SIDEBAR_ACTIVE_TAB, - payload: tabName, -}); - -export const setWorkspaceActiveTab = (tabName) => ({ - type: types.SET_WORKSPACE_ACTIVE_TAB, - payload: tabName, -}); - -export const setResponsePaneActiveTab = (tabName) => ({ - type: types.SET_RESPONSE_PANE_ACTIVE_TAB, - payload: tabName, -}); +import * as types from './actionTypes'; + +// BUSINESS LOGIC ACTIONS + +// action creators +export const getHistory = (history) => ({ + type: types.GET_HISTORY, + payload: history, +}); + +export const deleteFromHistory = (reqRes) => ({ + type: types.DELETE_HISTORY, + payload: reqRes, +}); + +export const clearHistory = () => ({ + type: types.CLEAR_HISTORY, +}); + +export const getCollections = (collections) => ({ + type: types.GET_COLLECTIONS, + payload: collections, +}); + +export const deleteFromCollection = (collection) => ({ + type: types.DELETE_COLLECTION, + payload: collection, +}); + +export const collectionToReqRes = (reqResArray) => ({ + type: types.COLLECTION_TO_REQRES, + payload: reqResArray, +}); + +export const collectionAdd = (collection) => ({ + type: types.COLLECTION_ADD, + payload: collection, +}); + +export const collectionUpdate = (collection) => ({ + type: types.COLLECTION_UPDATE, + payload: collection, +}); + +export const reqResClear = () => ({ + type: types.REQRES_CLEAR, +}); + +export const reqResAdd = (reqRes) => ({ + type: types.REQRES_ADD, + payload: reqRes, +}); + +export const reqResDelete = (reqRes) => ({ + type: types.REQRES_DELETE, + payload: reqRes, +}); + +export const reqResUpdate = (reqRes) => ({ + type: types.REQRES_UPDATE, + payload: reqRes, +}); + +export const scheduledReqResUpdate = (reqRes) => ({ + type: types.SCHEDULED_REQRES_UPDATE, + payload: reqRes, +}); + +export const scheduledReqResDelete = () => ({ + type: types.SCHEDULED_REQRES_DELETE, +}); + +export const updateGraph = (id) => ({ + type: types.UPDATE_GRAPH, + payload: id, +}); + +export const clearGraph = (reqRes) => ({ + type: types.CLEAR_GRAPH, + payload: reqRes, +}); + +export const clearAllGraph = () => ({ + type: types.CLEAR_ALL_GRAPH, +}); + +export const setComposerWarningMessage = (message) => ({ + type: types.SET_COMPOSER_WARNING_MESSAGE, + payload: message, +}); + +export const resetComposerFields = () => ({ + type: types.RESET_COMPOSER_FIELDS, +}); + +export const setNewRequestFields = (requestObj) => ({ + type: types.SET_NEW_REQUEST_FIELDS, + payload: requestObj, +}); + +export const setNewRequestHeaders = (headers) => ({ + type: types.SET_NEW_REQUEST_HEADERS, + payload: headers, +}); + +export const setNewRequestStreams = (streams) => ({ + type: types.SET_NEW_REQUEST_STREAMS, + payload: streams, +}); + +export const setNewRequestBody = (body) => ({ + type: types.SET_NEW_REQUEST_BODY, + payload: body, +}); + +export const setNewTestContent = (content) => ({ + type: types.SET_NEW_TEST_CONTENT, + payload: content, +}); + +export const setNewRequestCookies = (cookies) => ({ + type: types.SET_NEW_REQUEST_COOKIES, + payload: cookies, +}); + +export const setNewRequestSSE = (SSEBool) => ({ + type: types.SET_NEW_REQUEST_SSE, + payload: SSEBool, +}); + +export const setCurrentTab = (tab) => ({ + type: types.SET_CURRENT_TAB, + payload: tab, +}); + +export const setChecksAndMinis = (reqResArray) => ({ + type: types.SET_CHECKS_AND_MINIS, + payload: reqResArray, +}); + +export const setIntrospectionData = (dataObj) => ({ + type: types.SET_INTROSPECTION_DATA, + payload: dataObj, +}); + +export const saveCurrentResponseData = (dataObj, callingFunc) => ({ + type: types.SAVE_CURRENT_RESPONSE_DATA, + payload: dataObj, + callingFunc, +}); + +// UI ACTIONS + +export const setComposerDisplay = (composerDisplay) => ({ + type: types.SET_COMPOSER_DISPLAY, + payload: composerDisplay, +}); + +export const setSidebarActiveTab = (tabName) => ({ + type: types.SET_SIDEBAR_ACTIVE_TAB, + payload: tabName, +}); + +export const setWorkspaceActiveTab = (tabName) => ({ + type: types.SET_WORKSPACE_ACTIVE_TAB, + payload: tabName, +}); + +export const setResponsePaneActiveTab = (tabName) => ({ + type: types.SET_RESPONSE_PANE_ACTIVE_TAB, + payload: tabName, +}); + +// OPENAPI ACTIONS + +export const setNewRequestsOpenAPI = ({ + openapiMetadata, + openapiReqArray, +}) => ({ + type: types.SET_NEW_REQUESTS_OPENAPI, + payload: { openapiMetadata, openapiReqArray }, +}); +export const setOpenAPIServersGlobal = (serverIds) => ({ + type: types.SET_OPENAPI_SERVERS_GLOBAL, + payload: serverIds, +}); +export const setOpenAPIServers = (requestId, serverIds) => ({ + type: types.SET_NEW_OPENAPI_SERVERS, + payload: { id: requestId, serverIds }, +}); +export const setOpenAPIParameter = (requestId, location, name, value) => ({ + type: types.SET_NEW_OPENAPI_PARAMETER, + payload: { id: requestId, location, name, value }, +}); +export const setOpenAPIRequestBody = (requestId, mediaType, requestBody) => ({ + type: types.SET_NEW_OPENAPI_REQUEST_BODY, + payload: { id: requestId, mediaType, requestBody }, +}); +export const queueOpenAPIRequests = () => ({ + type: types.QUEUE_OPENAPI_REQUESTS, +}); diff --git a/src/client/components/composer/ComposerContainer.jsx b/src/client/components/composer/ComposerContainer.jsx index 6ba8bf3e3..d7be5a48d 100644 --- a/src/client/components/composer/ComposerContainer.jsx +++ b/src/client/components/composer/ComposerContainer.jsx @@ -1,194 +1,251 @@ -import React from "react"; -import { connect } from "react-redux"; -// import { Route, Switch, Link } from 'react-router-dom'; -import * as actions from "../../actions/actions"; - -import NetworkDropdown from "./NetworkDropdown"; -import RestContainer from "./RestContainer.jsx"; -import GraphQLContainer from "./GraphQLContainer.jsx"; -import GRPCContainer from "./GRPCContainer.jsx"; -import WSContainer from "./WSContainer.jsx"; - -const mapStateToProps = (store) => { - return { - reqResArray: store.business.reqResArray, - composerDisplay: store.ui.composerDisplay, - newRequestFields: store.business.newRequestFields, - newRequestHeaders: store.business.newRequestHeaders, - newRequestStreams: store.business.newRequestStreams, - newRequestBody: store.business.newRequestBody, - newRequestCookies: store.business.newRequestCookies, - newRequestSSE: store.business.newRequestSSE, - currentTab: store.business.currentTab, - warningMessage: store.business.warningMessage, - introspectionData: store.business.introspectionData, - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - reqResAdd: (reqRes) => { - dispatch(actions.reqResAdd(reqRes)); - }, - setComposerWarningMessage: (message) => { - dispatch(actions.setComposerWarningMessage(message)); - }, - setComposerDisplay: (composerDisplay) => { - dispatch(actions.setComposerDisplay(composerDisplay)); - }, - setNewRequestHeaders: (requestHeadersObj) => { - dispatch(actions.setNewRequestHeaders(requestHeadersObj)); - }, - setNewRequestStreams: (requestStreamsObj) => { - dispatch(actions.setNewRequestStreams(requestStreamsObj)); - }, - setNewRequestFields: (requestFields) => { - dispatch(actions.setNewRequestFields(requestFields)); - }, - setNewRequestBody: (requestBodyObj) => { - dispatch(actions.setNewRequestBody(requestBodyObj)); - }, - setNewTestContent: (testContent) => { - dispatch(actions.setNewTestContent(testContent)); - }, - setNewRequestCookies: (requestCookiesObj) => { - dispatch(actions.setNewRequestCookies(requestCookiesObj)); - }, - setNewRequestSSE: (requestSSEBool) => { - dispatch(actions.setNewRequestSSE(requestSSEBool)); - }, - resetComposerFields: () => { - dispatch(actions.resetComposerFields()); - }, - setWorkspaceActiveTab: (tabName) => { - dispatch(actions.setWorkspaceActiveTab(tabName)); - }, -}); - -const ComposerContainer = (props) => { - const onProtocolSelect = (network) => { - if (props.warningMessage.uri) { - const warningMessage = { ...props.warningMessage }; - delete warningMessage.uri; - props.setComposerWarningMessage({ ...warningMessage }); - } - props.setComposerWarningMessage({}); - switch (network) { - case "graphQL": { - props.resetComposerFields(); - //if graphql - props.setNewRequestFields({ - ...props.newRequestFields, - protocol: "", - url: props.newRequestFields.gqlUrl, - method: "QUERY", - graphQL: true, - gRPC: false, - network, - testContent: "", - }); - props.setNewRequestBody({ - //when switching to GQL clear body - ...props.newRequestBody, - bodyType: "GQL", - bodyContent: `query { - -}`, - bodyVariables: "", - }); - break; - } - case "rest": { - props.resetComposerFields(); - //if http/s - props.setNewRequestFields({ - ...props.newRequestFields, - protocol: "", - url: props.newRequestFields.restUrl, - method: "GET", - graphQL: false, - gRPC: false, - network, - testContent: "", - }); - props.setNewRequestBody({ - //when switching to http clear body - ...props.newRequestBody, - bodyType: "none", - bodyContent: ``, - }); - break; - } - case "grpc": { - props.resetComposerFields(); - //if gRPC - props.setNewRequestFields({ - ...props.newRequestFields, - protocol: "", - url: props.newRequestFields.grpcUrl, - method: "", - graphQL: false, - gRPC: true, - network, - testContent: "", - }); - props.setNewRequestBody({ - //when switching to gRPC clear body - ...props.newRequestBody, - bodyType: "GRPC", - bodyContent: ``, - }); - break; - } - case "ws": { - props.resetComposerFields(); - //if ws - props.setNewRequestFields({ - ...props.newRequestFields, - protocol: "", - url: props.newRequestFields.wsUrl, - method: "", - graphQL: false, - gRPC: false, - network, - testContent: "", - }); - props.setNewRequestBody({ - ...props.newRequestBody, - bodyType: "none", - bodyContent: "", - }); - break; - } - default: - } - }; - - return ( -
- {/* DROPDOWN PROTOCOL SELECTOR */} - - {/* BULMA TAB */} - - - {/* COMPOSER CONTENT ROUTING */} -
- {props.newRequestFields.network === "rest" && ( - - )} - {props.newRequestFields.network === "graphQL" && ( - - )} - {props.newRequestFields.network === "ws" && } - {props.newRequestFields.network === "grpc" && ( - - )} -
-
- ); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(ComposerContainer); +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { connect } from 'react-redux'; +import * as actions from '../../actions/actions'; +import NetworkDropdown from './NetworkDropdown'; +import RestContainer from './RestContainer.jsx'; +import OpenAPIContainer from './OpenAPIContainer.jsx'; +import GraphQLContainer from './GraphQLContainer.jsx'; +import GRPCContainer from './GRPCContainer.jsx'; +import WSContainer from './WSContainer.jsx'; +import WebRTCContainer from './WebRTCContainer'; + +const mapStateToProps = (store) => { + return { + reqResArray: store.business.reqResArray, + composerDisplay: store.ui.composerDisplay, + newRequestFields: store.business.newRequestFields, + newRequestHeaders: store.business.newRequestHeaders, + newRequestStreams: store.business.newRequestStreams, + newRequestBody: store.business.newRequestBody, + newRequestsOpenAPI: store.business.newRequestsOpenAPI, + newRequestCookies: store.business.newRequestCookies, + newRequestSSE: store.business.newRequestSSE, + currentTab: store.business.currentTab, + warningMessage: store.business.warningMessage, + introspectionData: store.business.introspectionData, + webrtcData: store.business.webrtcData, + }; +}; + +const mapDispatchToProps = (dispatch) => ({ + reqResAdd: (reqRes) => { + dispatch(actions.reqResAdd(reqRes)); + }, + setComposerWarningMessage: (message) => { + dispatch(actions.setComposerWarningMessage(message)); + }, + setComposerDisplay: (composerDisplay) => { + dispatch(actions.setComposerDisplay(composerDisplay)); + }, + setNewRequestHeaders: (requestHeadersObj) => { + dispatch(actions.setNewRequestHeaders(requestHeadersObj)); + }, + setNewRequestStreams: (requestStreamsObj) => { + dispatch(actions.setNewRequestStreams(requestStreamsObj)); + }, + setNewRequestFields: (requestFields) => { + dispatch(actions.setNewRequestFields(requestFields)); + }, + setNewRequestBody: (requestBodyObj) => { + dispatch(actions.setNewRequestBody(requestBodyObj)); + }, + setNewTestContent: (testContent) => { + dispatch(actions.setNewTestContent(testContent)); + }, + setNewRequestCookies: (requestCookiesObj) => { + dispatch(actions.setNewRequestCookies(requestCookiesObj)); + }, + setNewRequestSSE: (requestSSEBool) => { + dispatch(actions.setNewRequestSSE(requestSSEBool)); + }, + setNewRequestsOpenAPI: (parsedDocument) => { + dispatch(actions.setNewRequestsOpenAPI(parsedDocument)); + }, + resetComposerFields: () => { + dispatch(actions.resetComposerFields()); + }, + setWorkspaceActiveTab: (tabName) => { + dispatch(actions.setWorkspaceActiveTab(tabName)); + }, +}); + +const ComposerContainer = (props) => { + const onProtocolSelect = (network) => { + if (props.warningMessage.uri) { + const warningMessage = { ...props.warningMessage }; + delete warningMessage.uri; + props.setComposerWarningMessage({ ...warningMessage }); + } + props.setComposerWarningMessage({}); + switch (network) { + case 'graphQL': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: props.newRequestFields.gqlUrl, + method: 'QUERY', + graphQL: true, + gRPC: false, + webrtc: false, + network, + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'GQL', + bodyContent: `query { + +}`, + bodyVariables: '', + }); + break; + } + case 'rest': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: props.newRequestFields.restUrl, + method: 'GET', + graphQL: false, + webrtc: false, + gRPC: false, + network, + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'none', + bodyContent: ``, + }); + break; + } + case 'openapi': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: '', + method: 'GET', + graphQL: false, + gRPC: false, + ws: false, + network: 'openapi', + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'none', + bodyContent: '', + }); + break; + } + case 'grpc': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: props.newRequestFields.grpcUrl, + method: '', + graphQL: false, + gRPC: true, + webrtc: false, + network, + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'GRPC', + bodyContent: ``, + }); + break; + } + case 'ws': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: props.newRequestFields.wsUrl, + method: '', + graphQL: false, + gRPC: false, + webrtc: false, + network, + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'none', + bodyContent: '', + }); + break; + } + case 'webrtc': { + props.resetComposerFields(); + props.setNewRequestFields({ + ...props.newRequestFields, + protocol: '', + url: props.newRequestFields.webrtcUrl, + method: 'WebRTC', + graphQL: false, + gRPC: false, + ws: false, + webrtc: true, + network, + testContent: '', + }); + props.setNewRequestBody({ + ...props.newRequestBody, + bodyType: 'stun-ice', + bodyContent: { + iceConfiguration: { + iceServers: [ + { + urls: 'stun:stun1.l.google.com:19302', + }, + ], + }, + }, + }); + break; + } + + default: + } + }; + + return ( +
+ {/* DROPDOWN PROTOCOL SELECTOR */} + + + {/* COMPOSER CONTENT ROUTING */} +
+ {props.newRequestFields.network === 'rest' && ( + + )} + {props.newRequestFields.network === 'openapi' && ( + + )} + {props.newRequestFields.network === 'graphQL' && ( + + )} + {props.newRequestFields.network === 'ws' && } + {props.newRequestFields.network === 'grpc' && ( + + )} + {props.newRequestFields.network === 'webrtc' && ( + + )} +
+
+ ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(ComposerContainer); diff --git a/src/client/components/composer/GRPCContainer.jsx b/src/client/components/composer/GRPCContainer.jsx index 99b2ab63f..79973bb92 100644 --- a/src/client/components/composer/GRPCContainer.jsx +++ b/src/client/components/composer/GRPCContainer.jsx @@ -1,228 +1,210 @@ -import React from 'react' -import uuid from "uuid/v4"; // (Universally Unique Identifier)--generates a unique ID -import GRPCFormEditor from "./NewRequest/GRPCFormEditor.jsx"; -import HeaderEntryForm from "./NewRequest/HeaderEntryForm.jsx"; -import BodyEntryForm from "./NewRequest/BodyEntryForm.jsx"; -import FieldEntryForm from "./NewRequest/FieldEntryForm.jsx"; -import CookieEntryForm from "./NewRequest/CookieEntryForm.jsx"; -import historyController from "../../controllers/historyController"; -import GRPCTypeAndEndpointEntryForm from "./NewRequest/GRPCTypeAndEndpointEntryForm"; -import NewRequestButton from './NewRequest/NewRequestButton.jsx' -import TestEntryForm from './NewRequest/TestEntryForm' - -export default function GRPCContainer({ - resetComposerFields, - setNewRequestFields, - newRequestFields, - newRequestFields: { - gRPC, - url, - method, - protocol, - graphQL, - restUrl, - wsUrl, - gqlUrl, - grpcUrl, - network, - testContent, - }, - setNewRequestBody, - setNewTestContent, - newRequestBody, - newRequestBody: { - JSONFormatted, - rawType, - bodyContent, - bodyVariables, - bodyType, - }, - setNewRequestHeaders, - newRequestHeaders, - newRequestHeaders: { headersArr }, - setNewRequestCookies, - newRequestCookies, - newRequestCookies: { cookiesArr }, - setNewRequestStreams, - newRequestStreams, - newRequestStreams: { - selectedService, - selectedRequest, - selectedPackage, - streamingType, - initialQuery, - streamsArr, - streamContent, - services, - protoPath, - protoContent, - }, - setNewRequestSSE, - newRequestSSE: { isSSE }, - currentTab, - introspectionData, - setComposerWarningMessage, - setComposerDisplay, - warningMessage, - reqResAdd, - setWorkspaceActiveTab, -}) { - const requestValidationCheck = () => { - const validationMessage = {}; - //Error conditions... - if (newRequestFields.grpcUrl) return true; - else validationMessage.uri = "Enter a valid URI"; - return validationMessage; - }; - - const addNewRequest = () => { - const warnings = requestValidationCheck(); - if (Object.keys(warnings).length > 0) { - setComposerWarningMessage(warnings); - return; - } - let reqRes; - const protocol = "" - - // saves all stream body queries to history & reqres request body - let streamQueries = ""; - for (let i = 0; i < streamContent.length; i++) { - // queries MUST be in format, do NOT edit template literal unless necessary - streamQueries += `${streamContent[i]} - -`; - } - // define array to hold client query strings - const queryArrStr = streamContent; - const queryArr = []; - // scrub client query strings to remove line breaks - // convert strings to objects and push to array - for (let i = 0; i < queryArrStr.length; i += 1) { - let query = queryArrStr[i]; - const regexVar = /\r?\n|\r|↵/g; - query = query.replace(regexVar, ""); - queryArr.push(JSON.parse(query)); - } - // grabbing streaming type to set method in reqRes.request.method - const grpcStream = document.getElementById("stream").innerText; - // create reqres obj to be passed to controller for further actions/tasks - reqRes = { - id: uuid(), - created_at: new Date(), - protocol: "", - url, - graphQL, - gRPC, - timeSent: null, - timeReceived: null, - connection: "uninitialized", - connectionType: null, - checkSelected: false, - request: { - method: grpcStream, - headers: headersArr.filter( - (header) => header.active && !!header.key - ), - body: streamQueries, - bodyType, - rawType, - network, - restUrl, - wsUrl, - gqlUrl, - testContent: testContent || '', - grpcUrl, - }, - response: { - cookies: [], - headers: {}, - stream: null, - events: [], - }, - checked: false, - minimized: false, - tab: currentTab, - service: selectedService, - rpc: selectedRequest, - packageName: selectedPackage, - streamingType, - queryArr, - initialQuery, - streamsArr, - streamContent, - servicesObj: services, - protoPath, - protoContent, - }; - - // add request to history - historyController.addHistoryToIndexedDb(reqRes); - reqResAdd(reqRes); - - //reset for next request - resetComposerFields(); - - // GRPC REQUESTS - setNewRequestBody({ - ...newRequestBody, - bodyType: "GRPC", - rawType: "", - }); - setNewRequestFields({ - ...newRequestFields, - url: grpcUrl, - grpcUrl, - }); - - setWorkspaceActiveTab('workspace'); - }; - - const HeaderEntryFormStyle = { - //trying to change style to conditional created strange duplication effect when continuously changing protocol - display: !/wss?:\/\//.test(protocol) ? "block" : "none", - }; - return ( -
-
- - - - - -
-
- -
-
- ) -} \ No newline at end of file +import React from 'react'; +import uuid from 'uuid/v4'; +import GRPCProtoEntryForm from './NewRequest/GRPCProtoEntryForm.jsx'; +import HeaderEntryForm from './NewRequest/HeaderEntryForm.jsx'; +import historyController from '../../controllers/historyController'; +import GRPCTypeAndEndpointEntryForm from './NewRequest/GRPCTypeAndEndpointEntryForm'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; +import TestEntryForm from './NewRequest/TestEntryForm'; + +function GRPCContainer({ + resetComposerFields, + setNewRequestFields, + newRequestFields, + newRequestFields: { + gRPC, + url, + webrtc, + protocol, + graphQL, + restUrl, + wsUrl, + gqlUrl, + grpcUrl, + network, + testContent, + }, + setNewRequestBody, + setNewTestContent, + newRequestBody, + newRequestBody: { rawType, bodyType }, + setNewRequestHeaders, + newRequestHeaders, + newRequestHeaders: { headersArr }, + setNewRequestCookies, + newRequestCookies: { cookiesArr }, + setNewRequestStreams, + newRequestStreams, + newRequestStreams: { + selectedService, + selectedRequest, + selectedPackage, + streamingType, + initialQuery, + streamsArr, + streamContent, + services, + protoPath, + protoContent, + }, + newRequestSSE: { isSSE }, + currentTab, + setComposerWarningMessage, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const requestValidationCheck = () => { + const validationMessage = {}; + //Error conditions... + if (newRequestFields.grpcUrl) return true; + validationMessage.uri = 'Enter a valid URI'; + return validationMessage; + }; + + const addNewRequest = () => { + const warnings = requestValidationCheck(); + if (Object.keys(warnings).length > 0) { + setComposerWarningMessage(warnings); + return; + } + + // saves all stream body queries to history & reqres request body + let streamQueries = ''; + for (let i = 0; i < streamContent.length; i++) { + // queries MUST be in format, do NOT edit template literal unless necessary + streamQueries += `${streamContent[i]}`; + } + // define array to hold client query strings + const queryArrStr = streamContent; + const queryArr = []; + // scrub client query strings to remove line breaks + // convert strings to objects and push to array + for (let i = 0; i < queryArrStr.length; i += 1) { + let query = queryArrStr[i]; + const regexVar = /\r?\n|\r|↵/g; + query = query.replace(regexVar, ''); + queryArr.push(JSON.parse(query)); + } + // grabbing streaming type to set method in reqRes.request.method + const grpcStream = document.getElementById('stream').innerText; + // create reqres obj to be passed to controller for further actions/tasks + const reqRes = { + id: uuid(), + created_at: new Date(), + protocol: '', + url, + graphQL, + gRPC, + webrtc, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + request: { + method: grpcStream, + headers: headersArr.filter((header) => header.active && !!header.key), + body: streamQueries, + bodyType, + rawType, + network, + restUrl, + wsUrl, + gqlUrl, + testContent: testContent || '', + grpcUrl, + }, + response: { + cookies: [], + headers: {}, + stream: null, + events: [], + }, + checked: false, + minimized: false, + tab: currentTab, + service: selectedService, + rpc: selectedRequest, + packageName: selectedPackage, + streamingType, + queryArr, + initialQuery, + streamsArr, + streamContent, + servicesObj: services, + protoPath, + protoContent, + }; + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + + // GRPC REQUESTS + setNewRequestBody({ + ...newRequestBody, + bodyType: 'GRPC', + rawType: '', + }); + setNewRequestFields({ + ...newRequestFields, + url: grpcUrl, + grpcUrl, + }); + + setWorkspaceActiveTab('workspace'); + }; + + const HeaderEntryFormStyle = { + //trying to change style to conditional created strange duplication effect when continuously changing protocol + display: !/wss?:\/\//.test(protocol) ? 'block' : 'none', + }; + return ( +
+
+ + + + +
+
+ +
+
+ ); +} + +export default GRPCContainer; diff --git a/src/client/components/composer/GraphQLContainer.jsx b/src/client/components/composer/GraphQLContainer.jsx index d892a84b6..4f937d0a8 100644 --- a/src/client/components/composer/GraphQLContainer.jsx +++ b/src/client/components/composer/GraphQLContainer.jsx @@ -1,304 +1,283 @@ -import React from 'react' -import uuid from "uuid/v4"; // (Universally Unique Identifier)--generates a unique ID -import gql from "graphql-tag"; -import historyController from "../../controllers/historyController"; -import HeaderEntryForm from "./NewRequest/HeaderEntryForm.jsx"; -import FieldEntryForm from "./NewRequest/FieldEntryForm.jsx"; -import GraphQLMethodAndEndpointEntryForm from './NewRequest/GraphQLMethodAndEndpointEntryForm.jsx' -import CookieEntryForm from "./NewRequest/CookieEntryForm.jsx"; -import GraphQLBodyEntryForm from "./NewRequest/GraphQLBodyEntryForm.jsx"; -import GraphQLVariableEntryForm from "./NewRequest/GraphQLVariableEntryForm.jsx"; -import GraphQLIntrospectionLog from "./NewRequest/GraphQLIntrospectionLog.jsx"; -import NewRequestButton from './NewRequest/NewRequestButton.jsx'; -import TestEntryForm from "./NewRequest/TestEntryForm.jsx"; - -export default function GraphQLContainer({ - resetComposerFields, - setNewRequestFields, - newRequestFields, - newRequestFields: { - gRPC, - url, - method, - protocol, - graphQL, - restUrl, - wsUrl, - gqlUrl, - grpcUrl, - network, - testContent, - }, - setNewTestContent, - setNewRequestBody, - newRequestBody, - newRequestBody: { - JSONFormatted, - rawType, - bodyContent, - bodyVariables, - bodyType, - }, - setNewRequestHeaders, - newRequestHeaders, - newRequestHeaders: { headersArr }, - setNewRequestCookies, - newRequestCookies, - newRequestCookies: { cookiesArr }, - setNewRequestStreams, - newRequestStreams, - newRequestStreams: { - selectedService, - selectedRequest, - selectedPackage, - streamingType, - initialQuery, - streamsArr, - streamContent, - services, - protoPath, - protoContent, - }, - setNewRequestSSE, - newRequestSSE: { isSSE }, - currentTab, - introspectionData, - setComposerWarningMessage, - setComposerDisplay, - warningMessage, - reqResAdd, - setWorkspaceActiveTab, -}) { - const requestValidationCheck = () => { - const validationMessage = {}; - //Error conditions... - if (/https?:\/\/$|wss?:\/\/$/.test(url)) { - //if url is only http/https/ws/wss:// - validationMessage.uri = "Enter a valid URI"; - } - if (!/(https?:\/\/)|(wss?:\/\/)/.test(url)) { - //if url doesn't have http/https/ws/wss:// - validationMessage.uri = "Enter a valid URI"; - } - if (!JSONFormatted && rawType === "application/json") { - validationMessage.json = "Please fix JSON body formatting errors"; - } - if (method === "QUERY") { - if (url && !bodyContent) { - validationMessage.body = "GraphQL Body is Missing"; - } - if (url && bodyContent) { - try { - const body = gql` - ${bodyContent} - `; - } catch (e) { - console.log("error in gql-tag for client", e); - validationMessage.body = `Invalid graphQL body: \n ${e.message}`; - } - } - // need to add validation check for gql variables - } - return validationMessage; - }; - - const addNewRequest = () => { - const warnings = requestValidationCheck(); - if (Object.keys(warnings).length > 0) { - setComposerWarningMessage(warnings); - return; - } - - let reqRes; - const protocol = url.match(/(https?:\/\/)|(wss?:\/\/)/)[0]; - // HTTP && GRAPHQL QUERY & MUTATION REQUESTS - if (!/wss?:\/\//.test(protocol) && !gRPC) { - const URIWithoutProtocol = `${url.split(protocol)[1]}/`; - const host = protocol + URIWithoutProtocol.split("/")[0]; - let path = `/${URIWithoutProtocol.split("/") - .splice(1) - .join("/") - .replace(/\/{2,}/g, "/")}`; - if (path.charAt(path.length - 1) === "/" && path.length > 1) { - path = path.substring(0, path.length - 1); - } - path = path.replace(/https?:\//g, "http://"); - reqRes = { - id: uuid(), - created_at: new Date(), - protocol: url.match(/https?:\/\//)[0], - host, - path, - url, - graphQL, - gRPC, - timeSent: null, - timeReceived: null, - connection: "uninitialized", - connectionType: null, - checkSelected: false, - protoPath, - request: { - method, - headers: headersArr.filter( - (header) => header.active && !!header.key - ), - cookies: cookiesArr.filter( - (cookie) => cookie.active && !!cookie.key - ), - body: bodyContent || "", - bodyType, - bodyVariables: bodyVariables || "", - rawType, - isSSE, - network, - restUrl, - testContent: testContent || "", - wsUrl, - gqlUrl, - grpcUrl, - }, - response: { - headers: null, - events: null, - }, - checked: false, - minimized: false, - tab: currentTab, - }; - } - // GraphQL Subscriptions - const URIWithoutProtocol = `${url.split(protocol)[1]}/`; - const host = protocol + URIWithoutProtocol.split("/")[0]; - let path = `/${URIWithoutProtocol.split("/") - .splice(1) - .join("/") - .replace(/\/{2,}/g, "/")}`; - if (path.charAt(path.length - 1) === "/" && path.length > 1) { - path = path.substring(0, path.length - 1); - } - path = path.replace(/wss?:\//g, "ws://"); - reqRes = { - id: uuid(), - created_at: new Date(), - protocol: "ws://", - host, - path, - url, - graphQL, - gRPC, - timeSent: null, - timeReceived: null, - connection: "uninitialized", - connectionType: null, - checkSelected: false, - request: { - method, - headers: headersArr.filter( - (header) => header.active && !!header.key - ), - cookies: cookiesArr.filter( - (cookie) => cookie.active && !!cookie.key - ), - body: bodyContent || "", - bodyType, - bodyVariables: bodyVariables || "", - rawType, - network, - restUrl, - testContent: testContent || "", - wsUrl, - gqlUrl, - grpcUrl, - }, - response: { - headers: null, - events: null, - }, - checked: false, - minimized: false, - tab: currentTab, - }; - - - // add request to history - historyController.addHistoryToIndexedDb(reqRes); - reqResAdd(reqRes); - - //reset for next request - resetComposerFields(); - - // GRAPHQL REQUESTS - - setNewRequestBody({ - ...newRequestBody, - bodyType: "GQL", - rawType: "", - }); - setNewRequestFields({ - ...newRequestFields, - url: gqlUrl, - gqlUrl, - }); - - setWorkspaceActiveTab('workspace'); - }; - - return ( -
-
- - - - - - - - -
-
- -
-
- ) -} +import React from 'react'; +import uuid from 'uuid/v4'; +import gql from 'graphql-tag'; +import historyController from '../../controllers/historyController'; +import HeaderEntryForm from './NewRequest/HeaderEntryForm.jsx'; +import GraphQLMethodAndEndpointEntryForm from './NewRequest/GraphQLMethodAndEndpointEntryForm.jsx'; +import CookieEntryForm from './NewRequest/CookieEntryForm.jsx'; +import GraphQLBodyEntryForm from './NewRequest/GraphQLBodyEntryForm.jsx'; +import GraphQLVariableEntryForm from './NewRequest/GraphQLVariableEntryForm.jsx'; +import GraphQLIntrospectionLog from './NewRequest/GraphQLIntrospectionLog.jsx'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; +import TestEntryForm from './NewRequest/TestEntryForm.jsx'; + +function GraphQLContainer({ + resetComposerFields, + setNewRequestFields, + newRequestFields, + newRequestFields: { + gRPC, + webrtc, + url, + method, + graphQL, + restUrl, + wsUrl, + gqlUrl, + grpcUrl, + network, + testContent, + }, + setNewTestContent, + setNewRequestBody, + newRequestBody, + newRequestBody: { + JSONFormatted, + rawType, + bodyContent, + bodyVariables, + bodyType, + }, + setNewRequestHeaders, + newRequestHeaders, + newRequestHeaders: { headersArr }, + setNewRequestCookies, + newRequestCookies, + newRequestCookies: { cookiesArr }, + setNewRequestStreams, + newRequestStreams, + newRequestStreams: { protoPath }, + newRequestSSE: { isSSE }, + currentTab, + introspectionData, + setComposerWarningMessage, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const requestValidationCheck = () => { + const validationMessage = {}; + //Error conditions... + if (/https?:\/\/$|wss?:\/\/$/.test(url)) { + //if url is only http/https/ws/wss:// + validationMessage.uri = 'Enter a valid URI'; + } + if (!/(https?:\/\/)|(wss?:\/\/)/.test(url)) { + //if url doesn't have http/https/ws/wss:// + validationMessage.uri = 'Enter a valid URI'; + } + if (!JSONFormatted && rawType === 'application/json') { + validationMessage.json = 'Please fix JSON body formatting errors'; + } + if (method === 'QUERY') { + if (url && !bodyContent) { + validationMessage.body = 'GraphQL Body is Missing'; + } + if (url && bodyContent) { + try { + const body = gql` + ${bodyContent} + `; + } catch (e) { + console.log('error in gql-tag for client', e); + validationMessage.body = `Invalid graphQL body: \n ${e.message}`; + } + } + // need to add validation check for gql variables + } + return validationMessage; + }; + + const addNewRequest = () => { + const warnings = requestValidationCheck(); + if (Object.keys(warnings).length > 0) { + setComposerWarningMessage(warnings); + return; + } + + let reqRes; + const protocol = url.match(/(https?:\/\/)|(wss?:\/\/)/)[0]; + // HTTP && GRAPHQL QUERY & MUTATION REQUESTS + if (!/wss?:\/\//.test(protocol) && !gRPC) { + const URIWithoutProtocol = `${url.split(protocol)[1]}/`; + const host = protocol + URIWithoutProtocol.split('/')[0]; + let path = `/${URIWithoutProtocol.split('/') + .splice(1) + .join('/') + .replace(/\/{2,}/g, '/')}`; + if (path.charAt(path.length - 1) === '/' && path.length > 1) { + path = path.substring(0, path.length - 1); + } + path = path.replace(/https?:\//g, 'http://'); + reqRes = { + id: uuid(), + created_at: new Date(), + protocol: url.match(/https?:\/\//)[0], + host, + path, + url, + graphQL, + gRPC, + webrtc, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + protoPath, + request: { + method, + headers: headersArr.filter((header) => header.active && !!header.key), + cookies: cookiesArr.filter((cookie) => cookie.active && !!cookie.key), + body: bodyContent || '', + bodyType, + bodyVariables: bodyVariables || '', + rawType, + isSSE, + network, + restUrl, + testContent: testContent || '', + wsUrl, + gqlUrl, + grpcUrl, + }, + response: { + headers: null, + events: null, + }, + checked: false, + minimized: false, + tab: currentTab, + }; + } + // GraphQL Subscriptions + const URIWithoutProtocol = `${url.split(protocol)[1]}/`; + const host = protocol + URIWithoutProtocol.split('/')[0]; + let path = `/${URIWithoutProtocol.split('/') + .splice(1) + .join('/') + .replace(/\/{2,}/g, '/')}`; + if (path.charAt(path.length - 1) === '/' && path.length > 1) { + path = path.substring(0, path.length - 1); + } + path = path.replace(/wss?:\//g, 'ws://'); + reqRes = { + id: uuid(), + created_at: new Date(), + protocol: 'ws://', + host, + path, + url, + graphQL, + gRPC, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + request: { + method, + headers: headersArr.filter((header) => header.active && !!header.key), + cookies: cookiesArr.filter((cookie) => cookie.active && !!cookie.key), + body: bodyContent || '', + bodyType, + bodyVariables: bodyVariables || '', + rawType, + network, + restUrl, + testContent: testContent || '', + wsUrl, + gqlUrl, + grpcUrl, + }, + response: { + headers: null, + events: null, + }, + checked: false, + minimized: false, + tab: currentTab, + }; + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + + // GRAPHQL REQUESTS + + setNewRequestBody({ + ...newRequestBody, + bodyType: 'GQL', + rawType: '', + }); + setNewRequestFields({ + ...newRequestFields, + url: gqlUrl, + gqlUrl, + }); + + setWorkspaceActiveTab('workspace'); + }; + + return ( +
+
+ + + + + + + +
+
+ +
+
+ ); +} + +export default GraphQLContainer; diff --git a/src/client/components/composer/NetworkDropdown.jsx b/src/client/components/composer/NetworkDropdown.jsx index c78df1e95..746548baa 100644 --- a/src/client/components/composer/NetworkDropdown.jsx +++ b/src/client/components/composer/NetworkDropdown.jsx @@ -1,111 +1,135 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -import React, { useState, useRef, useEffect } from "react"; -import dropDownArrow from "../../../assets/icons/caret-down.svg"; -// import { Link } from 'react-router-dom'; - -export default function NetworkDropdown({ onProtocolSelect, network }) { - const [dropdownIsActive, setDropdownIsActive] = useState(); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - //console.log("event.target===>", event.target); - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - // The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node, i.e. the node itself, one of its direct children (childNodes), one of the children's direct children, and so on. - }; - document.addEventListener("click", closeDropdown); - return () => document.removeEventListener("click", closeDropdown); - }, []); - - let networkTitle = ""; - // eslint-disable-next-line default-case - switch (network) { - case "rest": - networkTitle = "REST"; - break; - case "graphQL": - networkTitle = "GRAPHQL"; - break; - case "grpc": - networkTitle = "gRPC"; - break; - case "ws": - networkTitle = "WEB SOCKETS"; - break; - } - - return ( - - ); -} +/* eslint-disable default-case */ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +import React, { useState, useRef, useEffect } from 'react'; +import dropDownArrow from '../../../assets/icons/caret-down.svg'; + +function NetworkDropdown({ onProtocolSelect, network }) { + const [dropdownIsActive, setDropdownIsActive] = useState(); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + let networkTitle = ''; + + switch (network) { + case 'rest': + networkTitle = 'REST'; + break; + case 'graphQL': + networkTitle = 'GRAPHQL'; + break; + case 'grpc': + networkTitle = 'gRPC'; + break; + case 'ws': + networkTitle = 'WEB SOCKETS'; + break; + case 'webrtc': + networkTitle = 'WebRTC'; + break; + case 'openapi': + networkTitle = 'OpenAPI'; + break; + } + + return ( + + ); +} + +export default NetworkDropdown; diff --git a/src/client/components/composer/NewRequest/BodyEntryForm.jsx b/src/client/components/composer/NewRequest/BodyEntryForm.jsx index 3f893166f..7d2fee813 100644 --- a/src/client/components/composer/NewRequest/BodyEntryForm.jsx +++ b/src/client/components/composer/NewRequest/BodyEntryForm.jsx @@ -1,104 +1,102 @@ -import React, { useState } from "react"; -import WWWForm from "./WWWForm.jsx"; -import BodyTypeSelect from "./BodyTypeSelect.jsx"; -import JSONTextArea from "./JSONTextArea.jsx"; -import RawBodyTypeSelect from "./RawBodyTypeSelect.jsx" -import JSONPrettify from "./JSONPrettify.jsx" -import TextCodeAreaEditable from "./TextCodeAreaEditable.jsx" - -const BodyEntryForm = (props) => { - // const [show, toggleShow] = useState(true); - const { - newRequestBody, - setNewRequestBody, - newRequestHeaders, - setNewRequestHeaders, - warningMessage, - } = props; - - - const bodyEntryArea = () => { - //BodyType of none : display nothing - if (newRequestBody.bodyType === "none") { - return; - } - //BodyType of XWWW... : display WWWForm entry - if (newRequestBody.bodyType === "x-www-form-urlencoded") { - return ( - - ); - } - //RawType of application/json : Text area box with error checking - if (newRequestBody.rawType === "application/json") { - return ( - - ); - } - - return ( - { - setNewRequestBody({ - ...newRequestBody, - bodyContent: value, - }); - }} - /> - ); - } - - - - return ( -
-
Body
-
- - - - {/* DROP DOWN MENU FOR SELECTING RAW TEXT TYPE */} - { newRequestBody.bodyType === "raw" && - - } - - { newRequestBody.bodyType === "raw" && - newRequestBody.rawType === 'application/json' && - - } -
- - { // conditionally render warning message - warningMessage ? -
-
{warningMessage.body || warningMessage.json}
-
- : null - } -
{bodyEntryArea()}
- -
- ); -}; - -export default BodyEntryForm; +import React from 'react'; +import WWWForm from './WWWForm.jsx'; +import BodyTypeSelect from './BodyTypeSelect.jsx'; +import JSONTextArea from './JSONTextArea.jsx'; +import RawBodyTypeSelect from './RawBodyTypeSelect.jsx'; +import JSONPrettify from './JSONPrettify.jsx'; +import TextCodeAreaEditable from './TextCodeAreaEditable.jsx'; + +const BodyEntryForm = (props) => { + const { + newRequestBody, + setNewRequestBody, + newRequestHeaders, + setNewRequestHeaders, + warningMessage, + } = props; + + const bodyEntryArea = () => { + //BodyType of none : display nothing + if (newRequestBody.bodyType === 'none') { + return; + } + //BodyType of XWWW... : display WWWForm entry + if (newRequestBody.bodyType === 'x-www-form-urlencoded') { + return ( + + ); + } + //RawType of application/json : Text area box with error checking + if (newRequestBody.rawType === 'application/json') { + return ( + + ); + } + + return ( + { + setNewRequestBody({ + ...newRequestBody, + bodyContent: value, + }); + }} + /> + ); + }; + + return ( +
+
Body
+
+ + + + {/* DROP DOWN MENU FOR SELECTING RAW TEXT TYPE */} + {newRequestBody.bodyType === 'raw' && ( + + )} + + {newRequestBody.bodyType === 'raw' && + newRequestBody.rawType === 'application/json' && ( + + )} +
+ + { + // conditionally render warning message + warningMessage ? ( +
+
{warningMessage.body || warningMessage.json}
+
+ ) : null + } +
+ {bodyEntryArea()} +
+
+ ); +}; + +export default BodyEntryForm; diff --git a/src/client/components/composer/NewRequest/BodyTypeSelect.jsx b/src/client/components/composer/NewRequest/BodyTypeSelect.jsx index 889096d91..10f5e2b12 100644 --- a/src/client/components/composer/NewRequest/BodyTypeSelect.jsx +++ b/src/client/components/composer/NewRequest/BodyTypeSelect.jsx @@ -1,130 +1,129 @@ -import React, { useState, useRef, useEffect } from "react"; -import PropTypes from "prop-types"; -import dropDownArrow from "../../../../assets/icons/caret-down-white.svg"; - -const classNames = require("classnames"); - -const BodyTypeSelect = (props) => { - const { - setNewRequestBody, - newRequestBody, - setNewRequestHeaders, - newRequestHeaders, - } = props; - - const [dropdownIsActive, setDropdownIsActive] = useState(); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - }; - document.addEventListener("click", closeDropdown); - return () => document.removeEventListener("click", closeDropdown); - }, []); - - const removeContentTypeHeader = () => { - const filtered = newRequestHeaders.headersArr.filter( - (header) => header.key.toLowerCase() !== "content-type" - ); - setNewRequestHeaders({ - headersArr: filtered, - count: filtered.length, - }); - }; - - const setNewBodyType = (bodyTypeStr) => { - setNewRequestBody({ - ...newRequestBody, - bodyType: bodyTypeStr, - }); - }; - - const setContentTypeHeader = (newBodyType) => { - const headersCopy = JSON.parse(JSON.stringify(newRequestHeaders)); - headersCopy.headersArr[0] = { - id: Math.random() * 1000000, - active: true, - key: "Content-type", - value: newBodyType, - }; - setNewRequestHeaders({ - headersArr: headersCopy.headersArr, - }); - }; - - return ( - - ); -}; - -BodyTypeSelect.propTypes = { - newRequestBody: PropTypes.object.isRequired, - setNewRequestBody: PropTypes.func.isRequired, -}; - -export default BodyTypeSelect; +import React, { useState, useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import dropDownArrow from '../../../../assets/icons/caret-down-white.svg'; + +const BodyTypeSelect = (props) => { + const { + setNewRequestBody, + newRequestBody, + setNewRequestHeaders, + newRequestHeaders, + } = props; + + const [dropdownIsActive, setDropdownIsActive] = useState(); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + const removeContentTypeHeader = () => { + const filtered = newRequestHeaders.headersArr.filter( + (header) => header.key.toLowerCase() !== 'content-type' + ); + setNewRequestHeaders({ + headersArr: filtered, + count: filtered.length, + }); + }; + + const setNewBodyType = (bodyTypeStr) => { + setNewRequestBody({ + ...newRequestBody, + bodyType: bodyTypeStr, + }); + }; + + const setContentTypeHeader = (newBodyType) => { + const headersCopy = JSON.parse(JSON.stringify(newRequestHeaders)); + headersCopy.headersArr[0] = { + id: Math.random() * 1000000, + active: true, + key: 'Content-type', + value: newBodyType, + }; + setNewRequestHeaders({ + headersArr: headersCopy.headersArr, + }); + }; + + return ( + + ); +}; + +BodyTypeSelect.propTypes = { + newRequestBody: PropTypes.object.isRequired, + setNewRequestBody: PropTypes.func.isRequired, +}; + +export default BodyTypeSelect; diff --git a/src/client/components/composer/NewRequest/ContentReqRowComposer.jsx b/src/client/components/composer/NewRequest/ContentReqRowComposer.jsx index 735d50e7b..c0a4c789e 100644 --- a/src/client/components/composer/NewRequest/ContentReqRowComposer.jsx +++ b/src/client/components/composer/NewRequest/ContentReqRowComposer.jsx @@ -1,34 +1,46 @@ -import React from 'react' - -export default function ContentReqRowComposer({ data, changeHandler, index, deleteItem, type }) { - return ( -
-
- changeHandler(data.id, 'active', e.target.checked)} - /> -
- changeHandler(data.id, 'key', e.target.value)} - placeholder='Key' - className="input" type="text" value={data.key} className="is-justify-content-center p-1 key" - /> - changeHandler(data.id, 'value', e.target.value)} - placeholder="Value" - className="input" type="text" value={data.value} className="is-justify-content-center is-flex-grow-4 p-1 value" - /> -
-
deleteItem(index)} /> -
- -
- ) -} +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/label-has-associated-control */ +import React from 'react'; + +export default function ContentReqRowComposer({ + data, + changeHandler, + index, + deleteItem, + type, +}) { + return ( +
+
+ changeHandler(data.id, 'active', e.target.checked)} + /> +
+ changeHandler(data.id, 'key', e.target.value)} + placeholder="Key" + className="input" + type="text" + value={data.key} + className="is-justify-content-center p-1 key" + /> + changeHandler(data.id, 'value', e.target.value)} + placeholder="Value" + className="input" + type="text" + value={data.value} + className="is-justify-content-center is-flex-grow-4 p-1 value" + /> +
+
deleteItem(index)} /> +
+
+ ); +} diff --git a/src/client/components/composer/NewRequest/CookieEntryForm.jsx b/src/client/components/composer/NewRequest/CookieEntryForm.jsx index 14adf5234..651df5536 100644 --- a/src/client/components/composer/NewRequest/CookieEntryForm.jsx +++ b/src/client/components/composer/NewRequest/CookieEntryForm.jsx @@ -1,121 +1,120 @@ -import React, { Component } from 'react'; -import ContentReqRowComposer from './ContentReqRowComposer.jsx'; - -class CookieEntryForm extends Component { - constructor(props) { - super(props); - this.state = { - show: false, - } - this.onChangeUpdateCookie = this.onChangeUpdateCookie.bind(this); - this.toggleShow = this.toggleShow.bind(this); - this.deleteCookie = this.deleteCookie.bind(this) - } - - componentDidMount() { - const cookiesDeepCopy = this.createDeepCookieCopy(); - if (cookiesDeepCopy[cookiesDeepCopy.length-1]?.key !== "") this.addCookie(cookiesDeepCopy); - } - - componentDidUpdate() { - const cookiesDeepCopy = this.createDeepCookieCopy(); - if (cookiesDeepCopy.length == 0) { - this.addCookie([]); - } - else if (cookiesDeepCopy[cookiesDeepCopy.length-1]?.key !== "") { - this.addCookie(cookiesDeepCopy); - } - } - - createDeepCookieCopy () { - return JSON.parse(JSON.stringify(this.props.newRequestCookies.cookiesArr)); - } - - addCookie(cookiesDeepCopy) { - cookiesDeepCopy.push({ - id: `cookie${this.props.newRequestCookies.count}`, - active: false, - key: '', - value: '' - }) - - this.props.setNewRequestCookies({ - cookiesArr: cookiesDeepCopy, - override: false, - count: cookiesDeepCopy.length, - }); - } - - onChangeUpdateCookie(id, field, value) { - const cookiesDeepCopy = this.createDeepCookieCopy(); - - //find cookie to update - let indexToBeUpdated; - for (let i = 0; i < cookiesDeepCopy.length; i++) { - if (cookiesDeepCopy[i].id === id) { - indexToBeUpdated = i; - break; - } - } - //update - cookiesDeepCopy[indexToBeUpdated][field] = value; - - //also switch checkbox if they are typing - if (field === 'key' || field === 'value') { - cookiesDeepCopy[indexToBeUpdated].active = true; - } - - - this.props.setNewRequestCookies({ - cookiesArr: cookiesDeepCopy, - count: cookiesDeepCopy.length, - }); - - } - - deleteCookie = (index) => { - const newCookies = this.createDeepCookieCopy(); - newCookies.splice(index, 1); - this.props.setNewRequestCookies({ - cookiesArr: newCookies, - count: newCookies.length, - }); - } - - toggleShow() { - this.setState({ - show: !this.state.show - }); - } - - render() { - const cookiesArr = this.props.newRequestCookies.cookiesArr.map((cookie, index) => ( - - )); - - return ( -
-
-
Cookies
- -
-
- {cookiesArr} -
-
- ); - } -} - -export default CookieEntryForm; \ No newline at end of file +import React, { Component } from 'react'; +import ContentReqRowComposer from './ContentReqRowComposer.jsx'; + +class CookieEntryForm extends Component { + constructor(props) { + super(props); + this.state = { + show: false, + }; + this.onChangeUpdateCookie = this.onChangeUpdateCookie.bind(this); + this.toggleShow = this.toggleShow.bind(this); + this.deleteCookie = this.deleteCookie.bind(this); + } + + componentDidMount() { + const cookiesDeepCopy = this.createDeepCookieCopy(); + if (cookiesDeepCopy[cookiesDeepCopy.length - 1]?.key !== '') + this.addCookie(cookiesDeepCopy); + } + + componentDidUpdate() { + const cookiesDeepCopy = this.createDeepCookieCopy(); + if (cookiesDeepCopy.length == 0) { + this.addCookie([]); + } else if (cookiesDeepCopy[cookiesDeepCopy.length - 1]?.key !== '') { + this.addCookie(cookiesDeepCopy); + } + } + + createDeepCookieCopy() { + return JSON.parse(JSON.stringify(this.props.newRequestCookies.cookiesArr)); + } + + addCookie(cookiesDeepCopy) { + cookiesDeepCopy.push({ + id: `cookie${this.props.newRequestCookies.count}`, + active: false, + key: '', + value: '', + }); + + this.props.setNewRequestCookies({ + cookiesArr: cookiesDeepCopy, + override: false, + count: cookiesDeepCopy.length, + }); + } + + onChangeUpdateCookie(id, field, value) { + const cookiesDeepCopy = this.createDeepCookieCopy(); + + //find cookie to update + let indexToBeUpdated; + for (let i = 0; i < cookiesDeepCopy.length; i++) { + if (cookiesDeepCopy[i].id === id) { + indexToBeUpdated = i; + break; + } + } + //update + cookiesDeepCopy[indexToBeUpdated][field] = value; + + //also switch checkbox if they are typing + if (field === 'key' || field === 'value') { + cookiesDeepCopy[indexToBeUpdated].active = true; + } + + this.props.setNewRequestCookies({ + cookiesArr: cookiesDeepCopy, + count: cookiesDeepCopy.length, + }); + } + + deleteCookie = (index) => { + const newCookies = this.createDeepCookieCopy(); + newCookies.splice(index, 1); + this.props.setNewRequestCookies({ + cookiesArr: newCookies, + count: newCookies.length, + }); + }; + + toggleShow() { + this.setState({ + show: !this.state.show, + }); + } + + render() { + const cookiesArr = this.props.newRequestCookies.cookiesArr.map( + (cookie, index) => ( + + ) + ); + + return ( +
+
+
Cookies
+ +
+
{cookiesArr}
+
+ ); + } +} + +export default CookieEntryForm; diff --git a/src/client/components/composer/NewRequest/FieldEntryForm.jsx b/src/client/components/composer/NewRequest/FieldEntryForm.jsx deleted file mode 100644 index 5f8e60e5b..000000000 --- a/src/client/components/composer/NewRequest/FieldEntryForm.jsx +++ /dev/null @@ -1,342 +0,0 @@ -/* eslint-disable default-case */ -import React, { useRef } from "react"; -// import ProtocolSelect from "./ProtocolSelect.jsx"; - -const FieldEntryForm = ({ - warningMessage, - setComposerWarningMessage, - setNewRequestFields, - newRequestFields, - setNewRequestBody, - newRequestBody, - setNewRequestHeaders, - newRequestStreams, - newRequestHeaders: { headersArr }, -}) => { - const onChangeHandler = (e, property, network) => { - const value = e.target.value; - if (warningMessage.uri) { - const warningMessage = { ...warningMessage }; - delete warningMessage.uri; - setComposerWarningMessage({ ...warningMessage }); - } - switch (property) { - case "url": { - const url = value; - if (network === "rest") { - setNewRequestFields({ - ...newRequestFields, - restUrl: url, - url, - }); - } - if (network === "ws") { - setNewRequestFields({ - ...newRequestFields, - wsUrl: url, - url, - }); - } - if (network === "graphQL") { - setNewRequestFields({ - ...newRequestFields, - gqlUrl: url, - url, - }); - } - if (network === "grpc") { - setNewRequestFields({ - ...newRequestFields, - grpcUrl: url, - url, - }); - } - break; - } - case "protocol": { - setComposerWarningMessage({}); - - if (network === "graphQL") { - //if graphql - setNewRequestFields({ - ...newRequestFields, - protocol: "", - url: newRequestFields.gqlUrl, - method: "QUERY", - graphQL: true, - gRPC: false, - network: "graphQL", - }); - setNewRequestBody({ - //when switching to GQL clear body - ...newRequestBody, - bodyType: "GQL", - bodyContent: `query { - -}`, - bodyVariables: "", - }); - break; - } else if (network === "rest") { - //if http/s - setNewRequestFields({ - ...newRequestFields, - protocol: "", - url: newRequestFields.restUrl, - method: "GET", - graphQL: false, - gRPC: false, - network: "rest", - }); - setNewRequestBody({ - //when switching to http clear body - ...newRequestBody, - bodyType: "none", - bodyContent: ``, - }); - break; - } else if (network === "grpc") { - //if gRPC - setNewRequestFields({ - ...newRequestFields, - protocol: "", - url: newRequestFields.grpcUrl, - method: "", - graphQL: false, - gRPC: true, - network: "grpc", - }); - setNewRequestBody({ - //when switching to gRPC clear body - ...newRequestBody, - bodyType: "GRPC", - bodyContent: ``, - }); - break; - } else if (network === "ws") { - //if ws - setNewRequestFields({ - ...newRequestFields, - protocol: value, - url: newRequestFields.wsUrl, - method: "", - graphQL: false, - gRPC: false, - network: "ws", - }); - setNewRequestBody({ - ...newRequestBody, - bodyType: "none", - bodyContent: "", - }); - } - //removes Content-Type Header - const filtered = headersArr.filter( - (header) => header.key.toLowerCase() !== "content-type" - ); - setNewRequestHeaders({ - headersArr: filtered, - count: filtered.length, - }); - break; - } - case "method": { - const methodReplaceRegex = new RegExp( - `${newRequestFields.method}`, - "mi" - ); - let newBody = ""; - if (!newRequestFields.graphQL && !newRequestFields.gRPC) { - //if one of 5 http methods (get, post, put, patch, delete) - setNewRequestBody({ - ...newRequestBody, - bodyType: "raw", - bodyContent: "", - }); - } - // GraphQL features - else if (value === "QUERY") { - //if switching to graphQL = true - if (!newRequestFields.graphQL) - newBody = `query { - -}`; - else - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace(methodReplaceRegex, "query") - : `query ${newRequestBody.bodyContent}`; - - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } else if (value === "MUTATION") { - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace(methodReplaceRegex, "mutation") - : `mutation ${newRequestBody.bodyContent}`; - - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } else if (value === "SUBSCRIPTION") { - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace( - methodReplaceRegex, - "subscription" - ) - : `subscription ${newRequestBody.bodyContent}`; - - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } - - //always set new method - setNewRequestFields({ - ...newRequestFields, - method: value, - protocol: value === "SUBSCRIPTION" ? "ws://" : "", - // url: value === "SUBSCRIPTION" ? "ws://" : "https://", - }); - } - } - }; - - const borderColor = warningMessage.uri ? "red" : "white"; - const inputEl = useRef(null); - const grpcStreamLabel = newRequestStreams.selectedStreamingType || "STREAM"; - return ( -
- {/* OLD PROTOCOL SELECTION COMPONENT */} - {/* */} - -
- ************** FieldEntryForm ************** - {/* below conditional method selection rendering for http/s */} - {newRequestFields.network === "rest" && ( -
- - { - onChangeHandler(e, "url", newRequestFields.network); - }} - ref={(input) => { - inputEl.current = input; - }} - /> -
- )} - - {/* below conditional rendering for ws */} - {newRequestFields.network === "ws" && ( - { - onChangeHandler(e, "url", newRequestFields.network); - }} - ref={(input) => { - inputEl.current = input; - }} - /> - )} - - {/* below conditional method selection rendering for graphql */} - {newRequestFields.network === "graphQL" && ( -
- - { - onChangeHandler(e, "url", newRequestFields.network); - }} - ref={(input) => { - inputEl.current = input; - }} - /> -
- )} - - {/* gRPC stream type button */} - {newRequestFields.network === "grpc" && ( -
- - { - onChangeHandler(e, "url", newRequestFields.network); - }} - ref={(input) => { - inputEl.current = input; - }} - /> -
- )} -
- - {warningMessage.uri && ( -
{warningMessage.uri}
- )} -
- ); -}; - -export default FieldEntryForm; diff --git a/src/client/components/composer/NewRequest/GRPCAutoInputForm.jsx b/src/client/components/composer/NewRequest/GRPCAutoInputForm.jsx index 987bb62b9..8de89729a 100644 --- a/src/client/components/composer/NewRequest/GRPCAutoInputForm.jsx +++ b/src/client/components/composer/NewRequest/GRPCAutoInputForm.jsx @@ -1,195 +1,197 @@ -import React, { useState, useEffect } from "react"; -import GRPCBodyEntryForm from "./GRPCBodyEntryForm.jsx"; -import GRPCServiceOrRequestSelect from "./GRPCServiceOrRequestSelect.jsx" - -const GRPCAutoInputForm = (props) => { - //component state for toggling show/hide - const [show, toggleShow] = useState(true); - //component state for service and request dropdown - const [serviceOption, setServiceOption] = useState("Select Service"); - const [requestOption, setRequestOption] = useState("Select Request"); - - const { - selectedService, - selectedRequest, - services, - streamsArr, - streamContent, - selectedPackage, - selectedStreamingType, - selectedServiceObj, - protoContent, - initialQuery - } = props.newRequestStreams; - - - // event handler for changes made to the Select Services dropdown list - const setService = (e) => { - setServiceOption(e.target.textContent); - const serviceName = - e.target.textContent !== "Select Service" ? e.target.textContent : null; - const serviceObj = services.find((ser) => ser.name === e.target.textContent); - // clears all stream query bodies except the first one - let streamsArr = [props.newRequestStreams.streamsArr[0]]; - let streamContent = [""]; - setRequestOption("Select Request"); - // the selected service name is saved in state of the store, mostly everything else is reset - props.setNewRequestStreams({ - ...props.newRequestStreams, - selectedService: serviceName, - selectedServiceObj: serviceObj, - selectedRequest: null, - selectedStreamingType: null, - selectedPackage: null, - streamsArr, - streamContent, - }); - }; - - // event handler for changes made to the Select Requests dropdown list - const setRequest = (e) => { - //update component state - setRequestOption(e.target.textContent); - //clear streams array and content except first index - const newStreamsArr = [streamsArr[0]]; - const newStreamContent = [streamContent[0]]; - - let requestName = e.target.textContent; - //clear stream bodies and set request to null if none selected - if (e.target.textContent === "Select Request") { - newStreamContent[0] = ""; - requestName = null; - } - - // clears all stream bodies except the first when switching from client/directional stream to something else - - // the selected request name is saved in state of the store - props.setNewRequestStreams({ - ...props.newRequestStreams, - selectedPackage: null, - selectedRequest: requestName, - selectedStreamingType: null, - streamContent: newStreamContent, - streamsArr: newStreamsArr, - count: 1, - }); - }; - - useEffect(() => { - //if no selected request or service object, return out and don't update - if (!selectedRequest || !selectedServiceObj) return; - //find rpc object that matches selectedRequest name - const rpc = selectedServiceObj.rpcs.find( - (rpc) => rpc.name === selectedRequest - ); - //find message object that matches rpc request name - const message = selectedServiceObj.messages.find((msg) => { - if (msg && msg.name === rpc.req) return msg; - }); - - //declare empty results obj that will become the initial query - const results = {}; - - if (message) { - // push each key/value pair of the message definition into the results obj - for (const key in message.def) { - // if message type is a nested message (message.def.nested === true) - if (message.def[key].nested) { - for (const submess of selectedServiceObj.messages) { - if (submess.name === message.def[key].dependent) { - // define obj for the submessage definition - const subObj = {}; - for (const subKey in submess.def) { - subObj[subKey] = submess.def[subKey].type - .slice(5) - .toLowerCase(); - } - results[key] = subObj; - break; - } - } - } else { - results[key] = message.def[key].type.slice(5).toLowerCase(); - } - } - } - //shallow copy streamsArr and streamCopy to reassign in store - const streamsArrCopy = [...streamsArr]; - const streamContentCopy = [...streamContent]; - - // push JSON formatted query in streamContent arr - const queryJSON = JSON.stringify(results, null, 4); - if (streamsArrCopy[0] !== "") { - streamsArrCopy[0].query = queryJSON; - } - // remove initial empty string then push new query to stream content arr - streamContentCopy.pop(); - streamContentCopy.push(queryJSON); - - props.setNewRequestStreams({ - ...props.newRequestStreams, - selectedPackage: selectedServiceObj.packageName, - selectedStreamingType: rpc.type, - streamsArr: streamsArrCopy, - streamContent: streamContentCopy, - initialQuery: queryJSON, - }); - }, [selectedRequest]); - - useEffect(() => { - setServiceOption(selectedService || "Select Service"); - setRequestOption(selectedRequest || "Select Request"); - }, [streamContent]); - - - //default options shown for services and request dropdowns - const servicesList = []; - const rpcsList = []; - - // autopopulates the service dropdown list - if (services) { - services.forEach((ser, i) => { - servicesList.push(ser.name) - }); - } - // autopopulates the request dropdown list - if (selectedServiceObj) { - for (let i = 0; i < selectedServiceObj.rpcs.length; i++) { - rpcsList.push(selectedServiceObj.rpcs[i].name); - } - } - - return ( -
-
Stream
- - - { serviceOption !== "Select Service" && - - } - - -
- ); -}; - -export default GRPCAutoInputForm; +/* eslint-disable */ +// Note: Do not enable eslint for this file because it will change the useEffect dependencies and break the tests. +import React, { useState, useEffect } from 'react'; +import GRPCBodyEntryForm from './GRPCBodyEntryForm.jsx'; +import GRPCServiceOrRequestSelect from './GRPCServiceOrRequestSelect.jsx'; + +const GRPCAutoInputForm = (props) => { + //component state for toggling show/hide + const [show, toggleShow] = useState(true); + //component state for service and request dropdown + const [serviceOption, setServiceOption] = useState('Select Service'); + const [requestOption, setRequestOption] = useState('Select Request'); + + const { + selectedService, + selectedRequest, + services, + streamsArr, + streamContent, + selectedPackage, + selectedStreamingType, + selectedServiceObj, + protoContent, + initialQuery, + } = props.newRequestStreams; + + // event handler for changes made to the Select Services dropdown list + const setService = (e) => { + setServiceOption(e.target.textContent); + const serviceName = + e.target.textContent !== 'Select Service' ? e.target.textContent : null; + const serviceObj = services.find( + (ser) => ser.name === e.target.textContent + ); + // clears all stream query bodies except the first one + let streamsArr = [props.newRequestStreams.streamsArr[0]]; + let streamContent = ['']; + setRequestOption('Select Request'); + // the selected service name is saved in state of the store, mostly everything else is reset + props.setNewRequestStreams({ + ...props.newRequestStreams, + selectedService: serviceName, + selectedServiceObj: serviceObj, + selectedRequest: null, + selectedStreamingType: null, + selectedPackage: null, + streamsArr, + streamContent, + }); + }; + + // event handler for changes made to the Select Requests dropdown list + const setRequest = (e) => { + //update component state + setRequestOption(e.target.textContent); + //clear streams array and content except first index + const newStreamsArr = [streamsArr[0]]; + const newStreamContent = [streamContent[0]]; + + let requestName = e.target.textContent; + //clear stream bodies and set request to null if none selected + if (e.target.textContent === 'Select Request') { + newStreamContent[0] = ''; + requestName = null; + } + + // clears all stream bodies except the first when switching from client/directional stream to something else + + // the selected request name is saved in state of the store + props.setNewRequestStreams({ + ...props.newRequestStreams, + selectedPackage: null, + selectedRequest: requestName, + selectedStreamingType: null, + streamContent: newStreamContent, + streamsArr: newStreamsArr, + count: 1, + }); + }; + + useEffect(() => { + //if no selected request or service object, return out and don't update + if (!selectedRequest || !selectedServiceObj) return; + //find rpc object that matches selectedRequest name + const rpc = selectedServiceObj.rpcs.find( + (rpc) => rpc.name === selectedRequest + ); + //find message object that matches rpc request name + const message = selectedServiceObj.messages.find((msg) => { + if (msg && msg.name === rpc.req) return msg; + }); + + //declare empty results obj that will become the initial query + const results = {}; + + if (message) { + // push each key/value pair of the message definition into the results obj + for (const key in message.def) { + // if message type is a nested message (message.def.nested === true) + if (message.def[key].nested) { + for (const submess of selectedServiceObj.messages) { + if (submess.name === message.def[key].dependent) { + // define obj for the submessage definition + const subObj = {}; + for (const subKey in submess.def) { + subObj[subKey] = submess.def[subKey].type + .slice(5) + .toLowerCase(); + } + results[key] = subObj; + break; + } + } + } else { + results[key] = message.def[key].type.slice(5).toLowerCase(); + } + } + } + //shallow copy streamsArr and streamCopy to reassign in store + const streamsArrCopy = [...streamsArr]; + const streamContentCopy = [...streamContent]; + + // push JSON formatted query in streamContent arr + const queryJSON = JSON.stringify(results, null, 4); + if (streamsArrCopy[0] !== '') { + streamsArrCopy[0].query = queryJSON; + } + // remove initial empty string then push new query to stream content arr + streamContentCopy.pop(); + streamContentCopy.push(queryJSON); + + props.setNewRequestStreams({ + ...props.newRequestStreams, + selectedPackage: selectedServiceObj.packageName, + selectedStreamingType: rpc.type, + streamsArr: streamsArrCopy, + streamContent: streamContentCopy, + initialQuery: queryJSON, + }); + }, [selectedRequest]); + + useEffect(() => { + setServiceOption(selectedService || 'Select Service'); + setRequestOption(selectedRequest || 'Select Request'); + }, [streamContent]); + + //default options shown for services and request dropdowns + const servicesList = []; + const rpcsList = []; + + // autopopulates the service dropdown list + if (services) { + services.forEach((ser, i) => { + servicesList.push(ser.name); + }); + } + // autopopulates the request dropdown list + if (selectedServiceObj) { + for (let i = 0; i < selectedServiceObj.rpcs.length; i++) { + rpcsList.push(selectedServiceObj.rpcs[i].name); + } + } + + return ( +
+
Stream
+ + + {serviceOption !== 'Select Service' && ( + + )} + + +
+ ); +}; + +export default GRPCAutoInputForm; diff --git a/src/client/components/composer/NewRequest/GRPCBodyEntryForm.jsx b/src/client/components/composer/NewRequest/GRPCBodyEntryForm.jsx index 6346c49a7..a50a62318 100644 --- a/src/client/components/composer/NewRequest/GRPCBodyEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GRPCBodyEntryForm.jsx @@ -1,115 +1,115 @@ -import React, { useState, useEffect } from "react"; -import GRPCBodyStream from "./GRPCBodyStream.jsx"; - -const GRPCBodyEntryForm = (props) => { - const [show, toggleShow] = useState(true); - - // when application first loads - useEffect(() => { - if (props.newRequestStreams.streamsArr.length === 0) { - const newStreamsArr = [ - { - id: props.newRequestStreams.count, - query: "", - }, - ]; - - props.newRequestStreams.streamContent.push(""); - // update state in the store - props.setNewRequestStreams({ - streamsArr: newStreamsArr, - count: newStreamsArr.length, - streamContent: props.newRequestStreams.streamContent, - }); - } - }, []); - - // add additional streams only for CLIENT or BIDIRECTIONAL streaming - const addStream = () => { - const streamsArr = [...props.newRequestStreams.streamsArr]; - const streamContent = [...props.newRequestStreams.streamContent]; - // save query of initial stream body - const firstBodyQuery = props.newRequestStreams.initialQuery; - // construct new stream body obj & push into the streamsArr - const newStream = {}; - newStream.id = props.newRequestStreams.count; - newStream.query = firstBodyQuery; - streamsArr.push(newStream); - // push query of initial stream body into streamContent array - streamContent.push(firstBodyQuery); - // update mew state in the store - props.setNewRequestStreams({ - ...props.newRequestStreams, - streamsArr, - count: streamsArr.length, - streamContent, - }); - }; - - // event handler that updates state in the store when typing into the stream query body - const onChangeUpdateStream = (streamID, value) => { - // props.saveChanges(false); - const streamsArr = [...props.newRequestStreams.streamsArr]; - const streamContent = [...props.newRequestStreams.streamContent]; - - for (let i = 0; i < streamsArr.length; i++) { - if (streamsArr[i].id === streamID) { - streamsArr[streamID].query = value; - streamContent[streamID] = value; - props.setNewRequestStreams({ - ...props.newRequestStreams, - streamsArr, - streamContent, - }); - } - } - }; - - // for each stream body in the streamArr, render the GRPCBodyStream component - const streamArr = props.newRequestStreams.streamsArr.map((stream, index) => ( - - )); - - //if client stream or bidirectional, the add stream btn will be rendered below the stream bodies - let addStreamBtn; - if ( - props.selectedStreamingType === "CLIENT STREAM" || - props.selectedStreamingType === "BIDIRECTIONAL" - ) { - addStreamBtn = ( - - ); - } - /* - pseudocode for the return section - - first div renders the arrow button along with the title "Body" - - renders the stream bodies depending on how many there are in the streamArr - - if client stream or bidirectional, the add stream btn will be rendered below the stream bodies - */ - return ( -
-
Body
-
{streamArr}
- {addStreamBtn} -
- ); -}; - -export default GRPCBodyEntryForm; +import React, { useState, useEffect } from 'react'; +import GRPCBodyStream from './GRPCBodyStream.jsx'; + +const GRPCBodyEntryForm = (props) => { + const [show, toggleShow] = useState(true); + + // when application first loads + useEffect(() => { + if (props.newRequestStreams.streamsArr.length === 0) { + const newStreamsArr = [ + { + id: props.newRequestStreams.count, + query: '', + }, + ]; + + props.newRequestStreams.streamContent.push(''); + // update state in the store + props.setNewRequestStreams({ + streamsArr: newStreamsArr, + count: newStreamsArr.length, + streamContent: props.newRequestStreams.streamContent, + }); + } + }, []); + + // add additional streams only for CLIENT or BIDIRECTIONAL streaming + const addStream = () => { + const streamsArr = [...props.newRequestStreams.streamsArr]; + const streamContent = [...props.newRequestStreams.streamContent]; + // save query of initial stream body + const firstBodyQuery = props.newRequestStreams.initialQuery; + // construct new stream body obj & push into the streamsArr + const newStream = {}; + newStream.id = props.newRequestStreams.count; + newStream.query = firstBodyQuery; + streamsArr.push(newStream); + // push query of initial stream body into streamContent array + streamContent.push(firstBodyQuery); + // update mew state in the store + props.setNewRequestStreams({ + ...props.newRequestStreams, + streamsArr, + count: streamsArr.length, + streamContent, + }); + }; + + // event handler that updates state in the store when typing into the stream query body + const onChangeUpdateStream = (streamID, value) => { + // props.saveChanges(false); + const streamsArr = [...props.newRequestStreams.streamsArr]; + const streamContent = [...props.newRequestStreams.streamContent]; + + for (let i = 0; i < streamsArr.length; i++) { + if (streamsArr[i].id === streamID) { + streamsArr[streamID].query = value; + streamContent[streamID] = value; + props.setNewRequestStreams({ + ...props.newRequestStreams, + streamsArr, + streamContent, + }); + } + } + }; + + // for each stream body in the streamArr, render the GRPCBodyStream component + const streamArr = props.newRequestStreams.streamsArr.map((stream, index) => ( + + )); + + //if client stream or bidirectional, the add stream btn will be rendered below the stream bodies + let addStreamBtn; + if ( + props.selectedStreamingType === 'CLIENT STREAM' || + props.selectedStreamingType === 'BIDIRECTIONAL' + ) { + addStreamBtn = ( + + ); + } + /* + pseudocode for the return section + - first div renders the arrow button along with the title "Body" + - renders the stream bodies depending on how many there are in the streamArr + - if client stream or bidirectional, the add stream btn will be rendered below the stream bodies + */ + return ( +
+
Body
+
{streamArr}
+ {addStreamBtn} +
+ ); +}; + +export default GRPCBodyEntryForm; diff --git a/src/client/components/composer/NewRequest/GRPCBodyStream.jsx b/src/client/components/composer/NewRequest/GRPCBodyStream.jsx index 75af126f6..78d1ac65c 100644 --- a/src/client/components/composer/NewRequest/GRPCBodyStream.jsx +++ b/src/client/components/composer/NewRequest/GRPCBodyStream.jsx @@ -1,86 +1,86 @@ -/* eslint-disable lines-between-class-members */ -import React, { useState, useEffect } from "react"; -import TextCodeAreaEditable from './TextCodeAreaEditable'; - -const GRPCBodyStream = (props) => { - const [showError, setError] = useState(null); - // event handler that allows the client to delete a stream body - // eslint-disable-next-line lines-between-class-members - const deleteStream = (id) => { - if (props.newRequestStreams.streamsArr.length === 1) { - setError("Error: Must have at least one stream body"); - return; - } - const streamsArr = [...props.newRequestStreams.streamsArr]; - const streamContent = [...props.newRequestStreams.streamContent]; - // delete the query from the streamContent arr and the stream body from streamsArr - streamContent.splice(id, 1); - streamsArr.splice(id, 1); - // reassign the id num of each subsequent stream body in the streamsArr after the deletion - for (let i = id; i < streamsArr.length; i++) { - streamsArr[i].id = i; - } - // update the state in the store - props.setNewRequestStreams({ - ...props.newRequestStreams, - streamsArr, - count: streamsArr.length, - streamContent, - }); - }; - - useEffect(() => { - if (showError) setError(null); - }, [props.newRequestStreams]); - - // grabs the query based on the stream id/number - const streamContentID = - props.newRequestStreams.streamContent[props.stream.id]; - // if none or the first stream query in the array - const streamBody = ( - props.changeHandler(props.stream.id, value)} - /> - ); - - // displays the stream number & delete btn next to the stream body for client or bidirectionbal streaming - let streamNum; - let deleteStreamBtn; - if ( - props.selectedStreamingType === "CLIENT STREAM" || - props.selectedStreamingType === "BIDIRECTIONAL" - ) { - streamNum = ( - - Stream {props.streamNum + 1} - - ); - deleteStreamBtn = ( - - ); - } - - // pseudocode for the return section: - // renders the stream body (and the stream number if for client or bidirectional stream) - return ( -
-
{showError}
-
-
- {deleteStreamBtn} - {streamNum} -
- {streamBody} -
-
- ); -}; - -export default GRPCBodyStream; +/* eslint-disable lines-between-class-members */ +import React, { useState, useEffect } from 'react'; +import TextCodeAreaEditable from './TextCodeAreaEditable'; + +const GRPCBodyStream = (props) => { + const [showError, setError] = useState(null); + // event handler that allows the client to delete a stream body + // eslint-disable-next-line lines-between-class-members + const deleteStream = (id) => { + if (props.newRequestStreams.streamsArr.length === 1) { + setError('Error: Must have at least one stream body'); + return; + } + const streamsArr = [...props.newRequestStreams.streamsArr]; + const streamContent = [...props.newRequestStreams.streamContent]; + // delete the query from the streamContent arr and the stream body from streamsArr + streamContent.splice(id, 1); + streamsArr.splice(id, 1); + // reassign the id num of each subsequent stream body in the streamsArr after the deletion + for (let i = id; i < streamsArr.length; i++) { + streamsArr[i].id = i; + } + // update the state in the store + props.setNewRequestStreams({ + ...props.newRequestStreams, + streamsArr, + count: streamsArr.length, + streamContent, + }); + }; + + useEffect(() => { + if (showError) setError(null); + }, [props.newRequestStreams]); + + // grabs the query based on the stream id/number + const streamContentID = + props.newRequestStreams.streamContent[props.stream.id]; + // if none or the first stream query in the array + const streamBody = ( + + props.changeHandler(props.stream.id, value) + } + /> + ); + + // displays the stream number & delete btn next to the stream body for client or bidirectionbal streaming + let streamNum; + let deleteStreamBtn; + if ( + props.selectedStreamingType === 'CLIENT STREAM' || + props.selectedStreamingType === 'BIDIRECTIONAL' + ) { + streamNum = ( + Stream {props.streamNum + 1} + ); + deleteStreamBtn = ( + + ); + } + + // pseudocode for the return section: + // renders the stream body (and the stream number if for client or bidirectional stream) + return ( +
+
{showError}
+
+
+ {deleteStreamBtn} + {streamNum} +
+ {streamBody} +
+
+ ); +}; + +export default GRPCBodyStream; diff --git a/src/client/components/composer/NewRequest/GRPCFormEditor.jsx b/src/client/components/composer/NewRequest/GRPCFormEditor.jsx deleted file mode 100644 index 383d573d8..000000000 --- a/src/client/components/composer/NewRequest/GRPCFormEditor.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { useState } from "react"; -import GRPCAutoInputForm from "./GRPCAutoInputForm.jsx"; -import TextCodeAreaEditable from "./TextCodeAreaEditable.jsx"; -// import protoParserFunc from "../../../protoParser.js"; - -const { api } = window; - -const GRPCProtoEntryForm = (props) => { - const [show, toggleShow] = useState(true); - const [protoError, showError] = useState(null); - const [changesSaved, saveChanges] = useState(false); - - // import proto file via electron file import dialog and have it displayed in proto textarea box - const importProtos = () => { - // clear all stream bodies except first one upon clicking on import proto file - let streamsArr = [props.newRequestStreams.streamsArr[0]]; - let streamContent = [""]; - // reset streaming type next to the URL & reset Select Service dropdown to default option - // reset selected package name, service, request, streaming type & protoContent - if (props.newRequestStreams.protoContent !== null) { - props.setNewRequestStreams({ - ...props.newRequestStreams, - selectedPackage: null, - selectedService: null, - selectedRequest: null, - selectedStreamingType: null, - services: [], - protoContent: "", - streamsArr, - streamContent, - count: 1, - }); - } - //listens for imported proto content from main process - api.receive("proto-info", (readProto, parsedProto) => { - saveChanges(true); - props.setNewRequestStreams({ - ...props.newRequestStreams, - protoContent: readProto, - services: parsedProto.serviceArr, - protoPath: parsedProto.protoPath, - }); - }); - - api.send("import-proto"); - }; - - // saves protoContent in the store whenever client make changes to proto file or pastes a copy - const updateProtoBody = (value) => { - showError(null); - props.setNewRequestStreams({ - ...props.newRequestStreams, - protoContent: value, - }); - saveChanges(false); - }; - - // update protoContent state in the store after making changes to the proto file - const submitUpdatedProto = () => { - //only update if changes aren't saved - if (!changesSaved) { - // parse new updated proto file and save to store - api.receive("protoParserFunc-return", (data) => { - if (data.error) { - showError( - ".proto parsing error: Please enter or import valid .proto" - ); - saveChanges(false); - } else { - showError(null); - saveChanges(true); - } - const services = data.serviceArr ? data.serviceArr : null; - const protoPath = data.protoPath ? data.protoPath : null; - const streamsArr = [props.newRequestStreams.streamsArr[0]]; - const streamContent = [""]; - - props.setNewRequestStreams({ - ...props.newRequestStreams, - selectedPackage: null, - selectedService: null, - selectedRequest: null, - selectedStreamingType: null, - selectedServiceObj: null, - services, - protoPath, - streamsArr, - streamContent, - count: 1, - }); - }); - - api.send("protoParserFunc-request", props.newRequestStreams.protoContent); - } - }; - - const saveChangesBtnText = changesSaved ? "Changes Saved" : "Save Changes"; - /* - pseudocode for the return section - - first div renders the arrow button along with the title "Proto" - - textarea has a default value which changes when a proto file is imported or pasted in - - the 2 buttons allow you to import a proto file or save any changes made to the textarea in the state of the store - - the GRPCAutoInputForm component renders the section with the dropdown lists for services and requests - */ - return ( -
-
-
Proto
-
- - -
-
- -
{protoError}
-
- updateProtoBody(value)} - value={props.newRequestStreams.protoContent} - mode="application/json" - /> -
- -
- ); -}; - -export default GRPCProtoEntryForm; diff --git a/src/client/components/composer/NewRequest/GRPCProtoEntryForm.jsx b/src/client/components/composer/NewRequest/GRPCProtoEntryForm.jsx index 9ad9cb1ad..2ca605e33 100644 --- a/src/client/components/composer/NewRequest/GRPCProtoEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GRPCProtoEntryForm.jsx @@ -1,145 +1,145 @@ -// import { POINT_CONVERSION_UNCOMPRESSED } from "constants"; -// import React, { useState, useEffect } from "react"; -// import GRPCAutoInputForm from "./GRPCAutoInputForm.jsx"; -// import TextCodeAreaEditable from "./TextCodeAreaEditable.jsx"; -// // import protoParserFunc from "../../../protoParser.js"; - -// const { api } = window; - -// const GRPCProtoEntryForm = (props) => { -// const [show, toggleShow] = useState(true); -// const [protoError, showError] = useState(null); -// const [changesSaved, saveChanges] = useState(false); -// // console.log("gRPC proto entry props new req streams --->", props.newRequestStreams) - -// // import proto file via electron file import dialog and have it displayed in proto textarea box -// const importProtos = () => { -// // clear all stream bodies except first one upon clicking on import proto file -// let streamsArr = [props.newRequestStreams.streamsArr[0]]; -// let streamContent = [""]; -// // reset streaming type next to the URL & reset Select Service dropdown to default option -// // reset selected package name, service, request, streaming type & protoContent -// if (props.newRequestStreams.protoContent !== null) { -// props.setNewRequestStreams({ -// ...props.newRequestStreams, -// selectedPackage: null, -// selectedService: null, -// selectedRequest: null, -// selectedStreamingType: null, -// services: [], -// protoContent: "", -// streamsArr, -// streamContent, -// count: 1, -// }); -// } -// //listens for imported proto content from main process -// api.receive("proto-info", (readProto, parsedProto) => { -// saveChanges(true); -// props.setNewRequestStreams({ -// ...props.newRequestStreams, -// protoContent: readProto, -// services: parsedProto.serviceArr, -// protoPath: parsedProto.protoPath, -// }); -// }); - -// api.send("import-proto"); -// }; - -// // saves protoContent in the store whenever client make changes to proto file or pastes a copy -// const updateProtoBody = (value) => { -// showError(null); -// props.setNewRequestStreams({ -// ...props.newRequestStreams, -// protoContent: value, -// }); -// saveChanges(false); -// }; - -// // update protoContent state in the store after making changes to the proto file -// const submitUpdatedProto = () => { -// //only update if changes aren't saved -// if (!changesSaved) { -// // parse new updated proto file and save to store -// api.receive("protoParserFunc-return", (data) => { -// if (data.error) { -// showError( -// ".proto parsing error: Please enter or import valid .proto" -// ); -// saveChanges(false); -// } else { -// showError(null); -// saveChanges(true); -// } -// const services = data.serviceArr ? data.serviceArr : null; -// const protoPath = data.protoPath ? data.protoPath : null; -// const streamsArr = [props.newRequestStreams.streamsArr[0]]; -// const streamContent = [""]; - -// props.setNewRequestStreams({ -// ...props.newRequestStreams, -// selectedPackage: null, -// selectedService: null, -// selectedRequest: null, -// selectedStreamingType: null, -// selectedServiceObj: null, -// services, -// protoPath, -// streamsArr, -// streamContent, -// count: 1, -// }); -// }); - -// api.send("protoParserFunc-request", props.newRequestStreams.protoContent); -// } -// }; - -// const saveChangesBtnText = changesSaved ? "Changes Saved" : "Save Changes"; -// /* -// pseudocode for the return section -// - first div renders the arrow button along with the title "Proto" -// - textarea has a default value which changes when a proto file is imported or pasted in -// - the 2 buttons allow you to import a proto file or save any changes made to the textarea in the state of the store -// - the GRPCAutoInputForm component renders the section with the dropdown lists for services and requests -// */ -// return ( -//
-//
-//
Proto
-//
-// -// -//
-//
- -//
{protoError}
-// updateProtoBody(value)} -// value={props.newRequestStreams.protoContent} -// mode="application/json" -// /> -// -//
-// ); -// }; - -// export default GRPCProtoEntryForm; +import React, { useState } from 'react'; +import GRPCAutoInputForm from './GRPCAutoInputForm.jsx'; +import TextCodeAreaEditable from './TextCodeAreaEditable.jsx'; +// import protoParserFunc from "../../../protoParser.js"; + +const { api } = window; + +const GRPCProtoEntryForm = (props) => { + const [show, toggleShow] = useState(true); + const [protoError, showError] = useState(null); + const [changesSaved, saveChanges] = useState(false); + + // import proto file via electron file import dialog and have it displayed in proto textarea box + const importProtos = () => { + // clear all stream bodies except first one upon clicking on import proto file + const streamsArr = [props.newRequestStreams.streamsArr[0]]; + const streamContent = ['']; + // reset streaming type next to the URL & reset Select Service dropdown to default option + // reset selected package name, service, request, streaming type & protoContent + if (props.newRequestStreams.protoContent !== null) { + props.setNewRequestStreams({ + ...props.newRequestStreams, + selectedPackage: null, + selectedService: null, + selectedRequest: null, + selectedStreamingType: null, + services: [], + protoContent: '', + streamsArr, + streamContent, + count: 1, + }); + } + //listens for imported proto content from main process + api.receive('proto-info', (readProto, parsedProto) => { + saveChanges(true); + props.setNewRequestStreams({ + ...props.newRequestStreams, + protoContent: readProto, + services: parsedProto.serviceArr, + protoPath: parsedProto.protoPath, + }); + }); + + api.send('import-proto'); + }; + + // saves protoContent in the store whenever client make changes to proto file or pastes a copy + const updateProtoBody = (value) => { + showError(null); + props.setNewRequestStreams({ + ...props.newRequestStreams, + protoContent: value, + }); + saveChanges(false); + }; + + // update protoContent state in the store after making changes to the proto file + const submitUpdatedProto = () => { + //only update if changes aren't saved + if (!changesSaved) { + // parse new updated proto file and save to store + api.receive('protoParserFunc-return', (data) => { + if (data.error) { + showError( + '.proto parsing error: Please enter or import valid .proto' + ); + saveChanges(false); + } else { + showError(null); + saveChanges(true); + } + const services = data.serviceArr ? data.serviceArr : null; + const protoPath = data.protoPath ? data.protoPath : null; + const streamsArr = [props.newRequestStreams.streamsArr[0]]; + const streamContent = ['']; + + props.setNewRequestStreams({ + ...props.newRequestStreams, + selectedPackage: null, + selectedService: null, + selectedRequest: null, + selectedStreamingType: null, + selectedServiceObj: null, + services, + protoPath, + streamsArr, + streamContent, + count: 1, + }); + }); + + api.send('protoParserFunc-request', props.newRequestStreams.protoContent); + } + }; + + const saveChangesBtnText = changesSaved ? 'Changes Saved' : 'Save Changes'; + /* + pseudocode for the return section + - first div renders the arrow button along with the title "Proto" + - textarea has a default value which changes when a proto file is imported or pasted in + - the 2 buttons allow you to import a proto file or save any changes made to the textarea in the state of the store + - the GRPCAutoInputForm component renders the section with the dropdown lists for services and requests + */ + return ( +
+
+
Proto
+
+ + +
+
+ +
{protoError}
+
+ updateProtoBody(value)} + value={props.newRequestStreams.protoContent} + mode="application/json" + /> +
+ +
+ ); +}; + +export default GRPCProtoEntryForm; diff --git a/src/client/components/composer/NewRequest/GRPCServiceOrRequestSelect.jsx b/src/client/components/composer/NewRequest/GRPCServiceOrRequestSelect.jsx index e613d40c2..786e6efdf 100644 --- a/src/client/components/composer/NewRequest/GRPCServiceOrRequestSelect.jsx +++ b/src/client/components/composer/NewRequest/GRPCServiceOrRequestSelect.jsx @@ -1,70 +1,73 @@ -import React, { useState, useRef, useEffect } from "react"; -import PropTypes from "prop-types"; -import dropDownArrow from "../../../../assets/icons/caret-down.svg"; - -const classNames = require("classnames"); - -const GRPCServiceOrRequestSelect = (props) => { - const { - value, - items, - onClick, - defaultTitle, - id, - } = props; - - const [dropdownIsActive, setDropdownIsActive] = useState(); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - } - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); - - - const listItems = []; - items.forEach((itemStr, index) => { - if (value !== itemStr){ - listItems.push( - { - setDropdownIsActive(false); - onClick(e); - }} - key={`listItem${index}`} - className="dropdown-item" - >{itemStr} - ); - } - }); - - return ( -
- -
- -
-
- { !!listItems.length && -
    - {listItems} -
- } -
-
- ); -} - -export default GRPCServiceOrRequestSelect; +import React, { useState, useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import dropDownArrow from '../../../../assets/icons/caret-down.svg'; + +const classNames = require('classnames'); + +const GRPCServiceOrRequestSelect = (props) => { + const { value, items, onClick, defaultTitle, id } = props; + + const [dropdownIsActive, setDropdownIsActive] = useState(); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + const listItems = []; + items.forEach((itemStr, index) => { + if (value !== itemStr) { + listItems.push( + { + setDropdownIsActive(false); + onClick(e); + }} + key={`listItem${index}`} + className="dropdown-item" + > + {itemStr} + + ); + } + }); + + return ( +
+
+ +
+
+ {!!listItems.length && ( +
    {listItems}
+ )} +
+
+ ); +}; + +export default GRPCServiceOrRequestSelect; diff --git a/src/client/components/composer/NewRequest/GRPCTypeAndEndpointEntryForm.jsx b/src/client/components/composer/NewRequest/GRPCTypeAndEndpointEntryForm.jsx index 769a97302..ee03f5a38 100644 --- a/src/client/components/composer/NewRequest/GRPCTypeAndEndpointEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GRPCTypeAndEndpointEntryForm.jsx @@ -1,56 +1,55 @@ -/* eslint-disable default-case */ -import React, { useRef } from "react"; - -const GRPCTypeAndEndpointEntryForm = ({ - warningMessage, - setComposerWarningMessage, - setNewRequestFields, - newRequestFields, - newRequestStreams, -}) => { - - const warningCheck = () => { - if (warningMessage.uri) { - const warningMessage = { ...warningMessage }; - delete warningMessage.uri; - setComposerWarningMessage({ ...warningMessage }); - } - } - - const urlChangeHandler = (e) => { - warningCheck(); - const url = e.target.value; - setNewRequestFields({ - ...newRequestFields, - grpcUrl: url, - url, - }); - } - - // TO DO - // change this to be initial state instead - const grpcStreamLabel = newRequestStreams.selectedStreamingType || "STREAM"; - - return ( -
- {/* button id is now stream for vanilla JS selector, this should change */} - - { - urlChangeHandler(e); - }} - /> - {warningMessage.uri && ( -
{warningMessage.uri}
- )} -
- ); -}; - -export default GRPCTypeAndEndpointEntryForm; +/* eslint-disable default-case */ +import React from 'react'; + +const GRPCTypeAndEndpointEntryForm = ({ + warningMessage, + setComposerWarningMessage, + setNewRequestFields, + newRequestFields, + newRequestStreams, +}) => { + const warningCheck = () => { + if (warningMessage.uri) { + const warningMessage = { ...warningMessage }; + delete warningMessage.uri; + setComposerWarningMessage({ ...warningMessage }); + } + }; + + const urlChangeHandler = (e) => { + warningCheck(); + const url = e.target.value; + setNewRequestFields({ + ...newRequestFields, + grpcUrl: url, + url, + }); + }; + + // TO DO + // change this to be initial state instead + const grpcStreamLabel = newRequestStreams.selectedStreamingType || 'STREAM'; + + return ( +
+ {/* button id is now stream for vanilla JS selector, this should change */} + + { + urlChangeHandler(e); + }} + /> + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default GRPCTypeAndEndpointEntryForm; diff --git a/src/client/components/composer/NewRequest/GraphQLBodyEntryForm.jsx b/src/client/components/composer/NewRequest/GraphQLBodyEntryForm.jsx index 6a299141f..10ab6d6fb 100644 --- a/src/client/components/composer/NewRequest/GraphQLBodyEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GraphQLBodyEntryForm.jsx @@ -1,79 +1,79 @@ -import React, { useState, useEffect } from "react"; -import { Controlled as CodeMirror } from "react-codemirror2"; -import "codemirror/addon/edit/matchbrackets"; -import "codemirror/addon/edit/closebrackets"; -import "codemirror/theme/twilight.css"; -import "codemirror/lib/codemirror.css"; -import "codemirror/addon/hint/show-hint"; -import "codemirror/addon/hint/show-hint.css"; -import "codemirror-graphql/hint"; -import "codemirror-graphql/lint"; -import "codemirror-graphql/mode"; -import "codemirror/addon/lint/lint.css"; - -const GraphQLBodyEntryForm = (props) => { - const { - newRequestBody, - newRequestBody: { bodyContent }, - newRequestBody: { bodyIsNew }, - setNewRequestBody, - warningMessage, - introspectionData, - } = props; - - const [cmValue, setValue] = useState(bodyContent); - - // set a new value for codemirror only if loading from history or changing query type - useEffect(() => { - if (!bodyIsNew) setValue(bodyContent); - }, [bodyContent]); - - return ( -
- { - // conditionally render warning message - warningMessage ?
{warningMessage.body}
: null - } -
Body
-
- { - editor.setSize("100%", 150); - }} - onBeforeChange={(editor, data, value) => { - const optionObj = { - schema: introspectionData.clientSchema, - completeSingle: false, - }; - setValue(value); - editor.setOption("lint", optionObj); - editor.setOption("hintOptions", optionObj); - }} - onChange={(editor, data, value) => { - editor.showHint(); - setNewRequestBody({ - ...newRequestBody, - bodyContent: value, - bodyIsNew: true, - }); - }} - /> -
-
- ); -}; - -export default GraphQLBodyEntryForm; +import React, { useState, useEffect } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/addon/edit/matchbrackets'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/theme/twilight.css'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/show-hint.css'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/mode'; +import 'codemirror/addon/lint/lint.css'; + +const GraphQLBodyEntryForm = (props) => { + const { + newRequestBody, + newRequestBody: { bodyContent }, + newRequestBody: { bodyIsNew }, + setNewRequestBody, + warningMessage, + introspectionData, + } = props; + + const [cmValue, setValue] = useState(bodyContent); + + // set a new value for codemirror only if loading from history or changing query type + useEffect(() => { + if (!bodyIsNew) setValue(bodyContent); + }, [bodyContent]); + + return ( +
+ { + // conditionally render warning message + warningMessage ?
{warningMessage.body}
: null + } +
Body
+
+ { + editor.setSize('100%', 150); + }} + onBeforeChange={(editor, data, value) => { + const optionObj = { + schema: introspectionData.clientSchema, + completeSingle: false, + }; + setValue(value); + editor.setOption('lint', optionObj); + editor.setOption('hintOptions', optionObj); + }} + onChange={(editor, data, value) => { + editor.showHint(); + setNewRequestBody({ + ...newRequestBody, + bodyContent: value, + bodyIsNew: true, + }); + }} + /> +
+
+ ); +}; + +export default GraphQLBodyEntryForm; diff --git a/src/client/components/composer/NewRequest/GraphQLIntrospectionLog.jsx b/src/client/components/composer/NewRequest/GraphQLIntrospectionLog.jsx index dcc26ae5b..dab0af6f1 100644 --- a/src/client/components/composer/NewRequest/GraphQLIntrospectionLog.jsx +++ b/src/client/components/composer/NewRequest/GraphQLIntrospectionLog.jsx @@ -1,40 +1,43 @@ -import React, { useState } from "react"; -import { useSelector, useDispatch } from 'react-redux'; -import graphQLController from "../../../controllers/graphQLController"; -import TextCodeAreaReadOnly from './TextCodeAreaReadOnly'; - - -const GraphQLIntrospectionLog = () => { - const headers = useSelector(store => store.business.newRequestHeaders.headersArr); - const cookies = useSelector(store => store.business.newRequestCookies.cookiesArr); - const introspectionData = useSelector(store => store.business.introspectionData); - const url = useSelector(store => store.business.newRequestFields.url); - - return ( -
- -
- {introspectionData === "Error: Please enter a valid GraphQL API URI" && -
{introspectionData}
- } - { !!introspectionData.schemaSDL && - {}} - /> - } - -
-
- ); -}; -export default GraphQLIntrospectionLog; +import React from 'react'; +import { useSelector } from 'react-redux'; +import graphQLController from '../../../controllers/graphQLController'; +import TextCodeAreaReadOnly from './TextCodeAreaReadOnly'; + +const GraphQLIntrospectionLog = () => { + const headers = useSelector( + (store) => store.business.newRequestHeaders.headersArr + ); + const cookies = useSelector( + (store) => store.business.newRequestCookies.cookiesArr + ); + const introspectionData = useSelector( + (store) => store.business.introspectionData + ); + const url = useSelector((store) => store.business.newRequestFields.url); + + return ( +
+ +
+ {introspectionData === + 'Error: Please enter a valid GraphQL API URI' && ( +
{introspectionData}
+ )} + {!!introspectionData.schemaSDL && ( + {}} + /> + )} +
+
+ ); +}; +export default GraphQLIntrospectionLog; diff --git a/src/client/components/composer/NewRequest/GraphQLMethodAndEndpointEntryForm.jsx b/src/client/components/composer/NewRequest/GraphQLMethodAndEndpointEntryForm.jsx index 88b60ee6f..7f0cb19c3 100644 --- a/src/client/components/composer/NewRequest/GraphQLMethodAndEndpointEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GraphQLMethodAndEndpointEntryForm.jsx @@ -1,175 +1,183 @@ -import React, { useState, useRef, useEffect } from "react"; -import dropDownArrow from "../../../../assets/icons/arrow_drop_down_white_192x192.png"; - -/* eslint-disable */ - -// import ProtocolSelect from "./ProtocolSelect.jsx"; - -const GraphQLMethodAndEndpointEntryForm = ({ - warningMessage, - setComposerWarningMessage, - setNewRequestFields, - newRequestFields, - setNewRequestBody, - newRequestBody, - setNewRequestHeaders, - newRequestStreams, - newRequestHeaders: { headersArr }, -}) => { - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - } - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); - - const warningCheck = () => { - if (warningMessage.uri) { - const newWarningMessage = { ...warningMessage }; - delete warningMessage.uri; - setComposerWarningMessage({ ...newWarningMessage }); - } - } - - const urlChangeHandler = (e) => { - warningCheck(); - const url = e.target.value; - - setNewRequestFields({ - ...newRequestFields, - gqlUrl: url, - url, - }); - } - - const methodChangeHandler = (value) => { - warningCheck(); - - let newBody; - const methodReplaceRegex = new RegExp( - `${newRequestFields.method}`, - "mi" - ); - // GraphQL features - if (value === "QUERY") { - if (!newRequestFields.graphQL){ - newBody = `query { - -}`; - } - else { - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace(methodReplaceRegex, "query") - : `query ${newRequestBody.bodyContent}`; - } - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } else if (value === "MUTATION") { - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace(methodReplaceRegex, "mutation") - : `mutation ${newRequestBody.bodyContent}`; - - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } else if (value === "SUBSCRIPTION") { - newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace( - methodReplaceRegex, - "subscription" - ) - : `subscription ${newRequestBody.bodyContent}`; - - setNewRequestBody({ - ...newRequestBody, - bodyContent: newBody, - bodyIsNew: false, - }); - } - - setNewRequestFields({ - ...newRequestFields, - method: value, - protocol: value === "SUBSCRIPTION" ? "ws://" : "", - }); - } - - return ( -
-
- -
- -
- - - - - { - urlChangeHandler(e); - }} - /> -
- - {warningMessage.uri && ( -
{warningMessage.uri}
- )} -
- ); -}; - - -export default GraphQLMethodAndEndpointEntryForm; +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState, useRef, useEffect } from 'react'; +import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; + +const GraphQLMethodAndEndpointEntryForm = ({ + warningMessage, + setComposerWarningMessage, + setNewRequestFields, + newRequestFields, + setNewRequestBody, + newRequestBody, + setNewRequestHeaders, + newRequestStreams, + newRequestHeaders: { headersArr }, +}) => { + const [dropdownIsActive, setDropdownIsActive] = useState(false); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + const warningCheck = () => { + if (warningMessage.uri) { + const newWarningMessage = { ...warningMessage }; + delete warningMessage.uri; + setComposerWarningMessage({ ...newWarningMessage }); + } + }; + + const urlChangeHandler = (e) => { + warningCheck(); + const url = e.target.value; + + setNewRequestFields({ + ...newRequestFields, + gqlUrl: url, + url, + }); + }; + + const methodChangeHandler = (value) => { + warningCheck(); + + let newBody; + const methodReplaceRegex = new RegExp(`${newRequestFields.method}`, 'mi'); + // GraphQL features + if (value === 'QUERY') { + if (!newRequestFields.graphQL) { + newBody = `query { + +}`; + } else { + newBody = methodReplaceRegex.test(newRequestBody.bodyContent) + ? newRequestBody.bodyContent.replace(methodReplaceRegex, 'query') + : `query ${newRequestBody.bodyContent}`; + } + setNewRequestBody({ + ...newRequestBody, + bodyContent: newBody, + bodyIsNew: false, + }); + } else if (value === 'MUTATION') { + newBody = methodReplaceRegex.test(newRequestBody.bodyContent) + ? newRequestBody.bodyContent.replace(methodReplaceRegex, 'mutation') + : `mutation ${newRequestBody.bodyContent}`; + + setNewRequestBody({ + ...newRequestBody, + bodyContent: newBody, + bodyIsNew: false, + }); + } else if (value === 'SUBSCRIPTION') { + newBody = methodReplaceRegex.test(newRequestBody.bodyContent) + ? newRequestBody.bodyContent.replace(methodReplaceRegex, 'subscription') + : `subscription ${newRequestBody.bodyContent}`; + + setNewRequestBody({ + ...newRequestBody, + bodyContent: newBody, + bodyIsNew: false, + }); + } + + setNewRequestFields({ + ...newRequestFields, + method: value, + protocol: value === 'SUBSCRIPTION' ? 'ws://' : '', + }); + }; + + return ( +
+
+
+ +
+ + + + { + urlChangeHandler(e); + }} + /> +
+ + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default GraphQLMethodAndEndpointEntryForm; diff --git a/src/client/components/composer/NewRequest/GraphQLVariableEntryForm.jsx b/src/client/components/composer/NewRequest/GraphQLVariableEntryForm.jsx index 265dd6490..15fab4689 100644 --- a/src/client/components/composer/NewRequest/GraphQLVariableEntryForm.jsx +++ b/src/client/components/composer/NewRequest/GraphQLVariableEntryForm.jsx @@ -1,65 +1,67 @@ -import React, { useState, useEffect, useRef } from "react"; -import { Controlled as CodeMirror } from "react-codemirror2"; -import "codemirror/addon/edit/matchbrackets"; -import "codemirror/addon/edit/closebrackets"; -import "codemirror/theme/twilight.css"; -import "codemirror/lib/codemirror.css"; -import "codemirror/addon/display/autorefresh" -import "codemirror/addon/display/placeholder" -import "codemirror/mode/javascript/javascript" - -const GraphQLVariableEntryForm = (props) => { - const { - newRequestBody: { bodyVariables }, - newRequestBody: { bodyIsNew }, - newRequestBody, - setNewRequestBody, - } = props; - - const [cmValue, setValue] = useState(bodyVariables); - - // ref to get the codemirror instance - const cmVariables = useRef(null); - - // set a new value for codemirror only if loading from history or changing gQL type - useEffect(() => { - if (!bodyIsNew) setValue(bodyVariables); - }, [bodyVariables]); - - return ( -
-
Variables
-
- { editor.setSize('100%', 100) }} - onBeforeChange={(editor, data, value) => { - setValue(value); - }} - onChange={(editor, data, value) => { - setNewRequestBody({ - ...newRequestBody, - bodyVariables: value, - bodyIsNew: true, - }); - }} - /> -
-
- ); -}; - -export default GraphQLVariableEntryForm; +import React, { useState, useEffect, useRef } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/addon/edit/matchbrackets'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/theme/twilight.css'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/addon/display/autorefresh'; +import 'codemirror/addon/display/placeholder'; +import 'codemirror/mode/javascript/javascript'; + +const GraphQLVariableEntryForm = (props) => { + const { + newRequestBody: { bodyVariables }, + newRequestBody: { bodyIsNew }, + newRequestBody, + setNewRequestBody, + } = props; + + const [cmValue, setValue] = useState(bodyVariables); + + // ref to get the codemirror instance + const cmVariables = useRef(null); + + // set a new value for codemirror only if loading from history or changing gQL type + useEffect(() => { + if (!bodyIsNew) setValue(bodyVariables); + }, [bodyVariables]); + + return ( +
+
Variables
+
+ { + editor.setSize('100%', 100); + }} + onBeforeChange={(editor, data, value) => { + setValue(value); + }} + onChange={(editor, data, value) => { + setNewRequestBody({ + ...newRequestBody, + bodyVariables: value, + bodyIsNew: true, + }); + }} + /> +
+
+ ); +}; + +export default GraphQLVariableEntryForm; diff --git a/src/client/components/composer/NewRequest/Header.jsx b/src/client/components/composer/NewRequest/Header.jsx deleted file mode 100644 index 5692361b6..000000000 --- a/src/client/components/composer/NewRequest/Header.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Header = ({ - type, content, changeHandler, Key, value, -}) => ( -
- ************** Header ************** - changeHandler(content.id, 'active', e.target.checked)} - /> - - changeHandler(content.id, 'key', e.target.value)} - /> - - changeHandler(content.id, 'value', e.target.value)} - /> -
-); - -const styles = { display: 'flex' }; - -Header.propTypes = { - content: PropTypes.object.isRequired, - changeHandler: PropTypes.func.isRequired, -}; - -export default Header; diff --git a/src/client/components/composer/NewRequest/HeaderEntryForm.jsx b/src/client/components/composer/NewRequest/HeaderEntryForm.jsx index 47fbb1242..a1cf2815d 100644 --- a/src/client/components/composer/NewRequest/HeaderEntryForm.jsx +++ b/src/client/components/composer/NewRequest/HeaderEntryForm.jsx @@ -1,223 +1,221 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable react/jsx-no-duplicate-props */ -import React, { Component } from "react"; -import Header from "./Header.jsx"; -import ContentReqRowComposer from "./ContentReqRowComposer.jsx"; -import { matchPath } from "react-router-dom"; - -class HeaderEntryForm extends Component { - constructor(props) { - super(props); - this.state = { - show: true, - }; - this.onChangeUpdateHeader = this.onChangeUpdateHeader.bind(this); - this.toggleShow = this.toggleShow.bind(this); - this.deleteHeader = this.deleteHeader.bind(this); - } - - componentDidUpdate() { - const headersDeepCopy = JSON.parse( - JSON.stringify(this.props.newRequestHeaders.headersArr) - ); - const lastHeader = headersDeepCopy[headersDeepCopy.length - 1]; - if ( - lastHeader?.key !== "" && - lastHeader?.key.toLowerCase() !== "content-type" - ) { - this.addHeader(); - } - this.checkContentTypeHeaderUpdate(); - } - - contentHeaderNeeded() { - const { method } = this.props.newRequestFields; - return ( - method === "PUT" || - method === "PATCH" || - method === "DELETE" || - method === "POST" || - this.props.newRequestBody.bodyType === "GQL" || - this.props.newRequestBody.bodyType === "GQLvariables" - ); - } - - checkContentTypeHeaderUpdate() { - let contentType; - - if ( - this.props.newRequestBody.bodyType === "GRPC" || - this.props.newRequestBody.bodyType === "none" - ) { - contentType = ""; - } else if (this.props.newRequestBody.bodyType === "x-www-form-urlencoded") { - contentType = "x-www-form-urlencoded"; - } else if ( - this.props.newRequestBody.bodyType === "GQL" || - this.props.newRequestBody.bodyType === "GQLvariables" - ) { - contentType = "application/json"; - } else { - contentType = this.props.newRequestBody.rawType; - } - - // Attempt to update header in these conditions: - const foundHeader = this.props.newRequestHeaders.headersArr.find((header) => - header.key.toLowerCase().includes("content-type") - ); - - // 1. if there is no contentTypeHeader, but there should be - if (!foundHeader && contentType !== "" && this.contentHeaderNeeded()) { - this.addContentTypeHeader(contentType); - // this.updateContentTypeHeader(contentType, foundHeader); - } - // 2. if there is a contentTypeHeader, but there SHOULDNT be, but the user inputs anyway... just let them - else if (foundHeader && contentType === "") { - //keeping this else if lets the user do what they want, it's fine, updateContentTypeHeader and removeContentTypeHeader will fix it later - } - // 3. if there is a contentTypeHeader, needs to update - // else if ( - // foundHeader && - // foundHeader.value !== contentType && - // this.contentHeaderNeeded() - // ) { - // this.updateContentTypeHeader(contentType, foundHeader); - // } - } - - addContentTypeHeader(contentType) { - if (!this.contentHeaderNeeded()) return; - const headersDeepCopy = JSON.parse( - JSON.stringify( - this.props.newRequestHeaders.headersArr.filter( - (header) => header.key.toLowerCase() !== "content-type" - ) - ) - ); - const contentTypeHeader = { - id: Math.random() * 1000000, - active: true, - key: "Content-Type", - value: contentType, - }; - headersDeepCopy.unshift(contentTypeHeader); - this.props.setNewRequestHeaders({ - headersArr: headersDeepCopy, - count: headersDeepCopy.length, - }); - } - - updateContentTypeHeader(contentType, foundHeader) { - const filtered = this.props.newRequestHeaders.headersArr.filter( - (header) => !header.key.toLowerCase().includes("content-type") - ); - - this.props.setNewRequestHeaders({ - headersArr: filtered, - count: filtered.length, - }); - } - - addHeader() { - const headersDeepCopy = JSON.parse( - JSON.stringify(this.props.newRequestHeaders.headersArr) - ); - headersDeepCopy.push({ - id: Math.random() * 1000000, - active: false, - key: "", - value: "", - }); - - this.props.setNewRequestHeaders({ - headersArr: headersDeepCopy, - override: false, - count: headersDeepCopy.length, - }); - } - - onChangeUpdateHeader(id, field, value) { - const headersDeepCopy = JSON.parse( - JSON.stringify(this.props.newRequestHeaders.headersArr) - ); - // find header to update - let indexToBeUpdated; - for (let i = 0; i < headersDeepCopy.length; i += 1) { - if (headersDeepCopy[i].id === id) { - indexToBeUpdated = i; - break; - } - } - // if it's the content-type header, just exit - const isFirst = indexToBeUpdated === 0; - // if (isFirst) return; - - // update - headersDeepCopy[indexToBeUpdated][field] = value; - - // also switch checkbox if they are typing - if (field === "key" || field === "value") { - headersDeepCopy[indexToBeUpdated].active = true; - } - - this.props.setNewRequestHeaders({ - headersArr: headersDeepCopy, - count: headersDeepCopy.length, - }); - } - - toggleShow() { - this.setState({ - show: !this.state.show, - }); - } - - deleteHeader(index) { - const newHeadersArr = this.props.newRequestHeaders.headersArr.slice(); - newHeadersArr.splice(index, 1); - this.props.setNewRequestHeaders({ - headersArr: newHeadersArr, - count: newHeadersArr.length, - }); - } - - render() { - let headerName = "Headers"; - let addHeaderName = "+ Header"; - // let headerClass = 'composer_submit http' - if (this.props.newRequestFields.gRPC) { - headerName = "Metadata"; - addHeaderName = "+ Metadata"; - } - - const headersArr = this.props.newRequestHeaders.headersArr.map( - (header, index) => ( - - ) - ); - - return ( -
-
-
{headerName}
- -
-
{headersArr}
-
- ); - } -} - -export default HeaderEntryForm; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable react/jsx-no-duplicate-props */ +import React, { Component } from 'react'; +import ContentReqRowComposer from './ContentReqRowComposer.jsx'; + +class HeaderEntryForm extends Component { + constructor(props) { + super(props); + this.state = { + show: true, + }; + this.onChangeUpdateHeader = this.onChangeUpdateHeader.bind(this); + this.toggleShow = this.toggleShow.bind(this); + this.deleteHeader = this.deleteHeader.bind(this); + } + + componentDidUpdate() { + const headersDeepCopy = JSON.parse( + JSON.stringify(this.props.newRequestHeaders.headersArr) + ); + const lastHeader = headersDeepCopy[headersDeepCopy.length - 1]; + if ( + lastHeader?.key !== '' && + lastHeader?.key.toLowerCase() !== 'content-type' + ) { + this.addHeader(); + } + this.checkContentTypeHeaderUpdate(); + } + + contentHeaderNeeded() { + const { method } = this.props.newRequestFields; + return ( + method === 'PUT' || + method === 'PATCH' || + method === 'DELETE' || + method === 'POST' || + this.props.newRequestBody.bodyType === 'GQL' || + this.props.newRequestBody.bodyType === 'GQLvariables' + ); + } + + checkContentTypeHeaderUpdate() { + let contentType; + + if ( + this.props.newRequestBody.bodyType === 'GRPC' || + this.props.newRequestBody.bodyType === 'none' + ) { + contentType = ''; + } else if (this.props.newRequestBody.bodyType === 'x-www-form-urlencoded') { + contentType = 'x-www-form-urlencoded'; + } else if ( + this.props.newRequestBody.bodyType === 'GQL' || + this.props.newRequestBody.bodyType === 'GQLvariables' + ) { + contentType = 'application/json'; + } else { + contentType = this.props.newRequestBody.rawType; + } + + // Attempt to update header in these conditions: + const foundHeader = this.props.newRequestHeaders.headersArr.find((header) => + header.key.toLowerCase().includes('content-type') + ); + + // 1. if there is no contentTypeHeader, but there should be + if (!foundHeader && contentType !== '' && this.contentHeaderNeeded()) { + this.addContentTypeHeader(contentType); + // this.updateContentTypeHeader(contentType, foundHeader); + } + // 2. if there is a contentTypeHeader, but there SHOULDNT be, but the user inputs anyway... just let them + else if (foundHeader && contentType === '') { + //keeping this else if lets the user do what they want, it's fine, updateContentTypeHeader and removeContentTypeHeader will fix it later + } + // 3. if there is a contentTypeHeader, needs to update + // else if ( + // foundHeader && + // foundHeader.value !== contentType && + // this.contentHeaderNeeded() + // ) { + // this.updateContentTypeHeader(contentType, foundHeader); + // } + } + + addContentTypeHeader(contentType) { + if (!this.contentHeaderNeeded()) return; + const headersDeepCopy = JSON.parse( + JSON.stringify( + this.props.newRequestHeaders.headersArr.filter( + (header) => header.key.toLowerCase() !== 'content-type' + ) + ) + ); + const contentTypeHeader = { + id: Math.random() * 1000000, + active: true, + key: 'Content-Type', + value: contentType, + }; + headersDeepCopy.unshift(contentTypeHeader); + this.props.setNewRequestHeaders({ + headersArr: headersDeepCopy, + count: headersDeepCopy.length, + }); + } + + updateContentTypeHeader(contentType, foundHeader) { + const filtered = this.props.newRequestHeaders.headersArr.filter( + (header) => !header.key.toLowerCase().includes('content-type') + ); + + this.props.setNewRequestHeaders({ + headersArr: filtered, + count: filtered.length, + }); + } + + addHeader() { + const headersDeepCopy = JSON.parse( + JSON.stringify(this.props.newRequestHeaders.headersArr) + ); + headersDeepCopy.push({ + id: Math.random() * 1000000, + active: false, + key: '', + value: '', + }); + + this.props.setNewRequestHeaders({ + headersArr: headersDeepCopy, + override: false, + count: headersDeepCopy.length, + }); + } + + onChangeUpdateHeader(id, field, value) { + const headersDeepCopy = JSON.parse( + JSON.stringify(this.props.newRequestHeaders.headersArr) + ); + // find header to update + let indexToBeUpdated; + for (let i = 0; i < headersDeepCopy.length; i += 1) { + if (headersDeepCopy[i].id === id) { + indexToBeUpdated = i; + break; + } + } + // if it's the content-type header, just exit + const isFirst = indexToBeUpdated === 0; + // if (isFirst) return; + + // update + headersDeepCopy[indexToBeUpdated][field] = value; + + // also switch checkbox if they are typing + if (field === 'key' || field === 'value') { + headersDeepCopy[indexToBeUpdated].active = true; + } + + this.props.setNewRequestHeaders({ + headersArr: headersDeepCopy, + count: headersDeepCopy.length, + }); + } + + toggleShow() { + this.setState({ + show: !this.state.show, + }); + } + + deleteHeader(index) { + const newHeadersArr = this.props.newRequestHeaders.headersArr.slice(); + newHeadersArr.splice(index, 1); + this.props.setNewRequestHeaders({ + headersArr: newHeadersArr, + count: newHeadersArr.length, + }); + } + + render() { + let headerName = 'Headers'; + let addHeaderName = '+ Header'; + // let headerClass = 'composer_submit http' + if (this.props.newRequestFields.gRPC) { + headerName = 'Metadata'; + addHeaderName = '+ Metadata'; + } + + const headersArr = this.props.newRequestHeaders.headersArr.map( + (header, index) => ( + + ) + ); + + return ( +
+
+
{headerName}
+ +
+
{headersArr}
+
+ ); + } +} + +export default HeaderEntryForm; diff --git a/src/client/components/composer/NewRequest/JSONPrettify.jsx b/src/client/components/composer/NewRequest/JSONPrettify.jsx index b4b215d55..046116157 100644 --- a/src/client/components/composer/NewRequest/JSONPrettify.jsx +++ b/src/client/components/composer/NewRequest/JSONPrettify.jsx @@ -1,28 +1,26 @@ -import React from 'react'; - -function JSONPrettify({ setNewRequestBody, newRequestBody }) { - - const prettyPrintJSON = () => { - const prettyString = JSON.stringify( - JSON.parse(newRequestBody.bodyContent), - null, - 4 - ); - setNewRequestBody({ - ...newRequestBody, - bodyContent: prettyString, - }); - } - - return ( - - ); - -} - -export default JSONPrettify; \ No newline at end of file +import React from 'react'; + +function JSONPrettify({ setNewRequestBody, newRequestBody }) { + const prettyPrintJSON = () => { + const prettyString = JSON.stringify( + JSON.parse(newRequestBody.bodyContent), + null, + 4 + ); + setNewRequestBody({ + ...newRequestBody, + bodyContent: prettyString, + }); + }; + + return ( + + ); +} + +export default JSONPrettify; diff --git a/src/client/components/composer/NewRequest/JSONTextArea.jsx b/src/client/components/composer/NewRequest/JSONTextArea.jsx index 8ac8af132..b4f344ff9 100644 --- a/src/client/components/composer/NewRequest/JSONTextArea.jsx +++ b/src/client/components/composer/NewRequest/JSONTextArea.jsx @@ -1,46 +1,45 @@ -import React, { useEffect } from "react"; -import PropTypes from "prop-types"; -import {UnControlled as CodeMirror} from 'react-codemirror2'; -import TextCodeAreaEditable from './TextCodeAreaEditable.jsx' - -export default function JSONTextArea({ newRequestBody, setNewRequestBody }) { - - useEffect(() => { - if (newRequestBody.bodyContent === "") { - setNewRequestBody({ - ...newRequestBody, - bodyContent: '{\n \n}', - }); - return; - } - try { - JSON.parse(newRequestBody.bodyContent); - if (!newRequestBody.JSONFormatted) { - setNewRequestBody({ - ...newRequestBody, - JSONFormatted: true, - }); - } - } catch (error) { - if (newRequestBody.JSONFormatted) { - setNewRequestBody({ - ...newRequestBody, - JSONFormatted: false, - }); - } - } - }); - - return ( - { - setNewRequestBody({ - ...newRequestBody, - bodyContent: value, - }); - }} - value={newRequestBody.bodyContent} - /> - ); -} \ No newline at end of file +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { UnControlled as CodeMirror } from 'react-codemirror2'; +import TextCodeAreaEditable from './TextCodeAreaEditable.jsx'; + +export default function JSONTextArea({ newRequestBody, setNewRequestBody }) { + useEffect(() => { + if (newRequestBody.bodyContent === '') { + setNewRequestBody({ + ...newRequestBody, + bodyContent: '{\n \n}', + }); + return; + } + try { + JSON.parse(newRequestBody.bodyContent); + if (!newRequestBody.JSONFormatted) { + setNewRequestBody({ + ...newRequestBody, + JSONFormatted: true, + }); + } + } catch (error) { + if (newRequestBody.JSONFormatted) { + setNewRequestBody({ + ...newRequestBody, + JSONFormatted: false, + }); + } + } + }); + + return ( + { + setNewRequestBody({ + ...newRequestBody, + bodyContent: value, + }); + }} + value={newRequestBody.bodyContent} + /> + ); +} diff --git a/src/client/components/composer/NewRequest/NewRequestButton.jsx b/src/client/components/composer/NewRequest/NewRequestButton.jsx index 33453f399..07a8a193a 100644 --- a/src/client/components/composer/NewRequest/NewRequestButton.jsx +++ b/src/client/components/composer/NewRequest/NewRequestButton.jsx @@ -1,13 +1,13 @@ -import React from 'react' - -const NewRequestButton = ({ onClick }) => ( - -); - -export default NewRequestButton; \ No newline at end of file +import React from 'react'; + +const NewRequestButton = ({ onClick }) => ( + +); + +export default NewRequestButton; diff --git a/src/client/components/composer/NewRequest/OpenAPIDocumentEntryForm.jsx b/src/client/components/composer/NewRequest/OpenAPIDocumentEntryForm.jsx new file mode 100644 index 000000000..847baad1c --- /dev/null +++ b/src/client/components/composer/NewRequest/OpenAPIDocumentEntryForm.jsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; + +const { api } = window; + +const OpenAPIDocumentEntryForm = (props) => { + const [protoError, showError] = useState(null); + + const importDocument = () => { + console.log('importing document'); + //listens for imported openapi document from main process + api.receive('openapi-info', (readDocument, parsedDocument) => { + console.log('received openapi-info'); + + props.setNewRequestsOpenAPI(parsedDocument); + }); + api.send('import-openapi'); + }; + + return ( +
+
+ +
+
+ ); +}; + +export default OpenAPIDocumentEntryForm; diff --git a/src/client/components/composer/NewRequest/OpenAPIEntryForm.jsx b/src/client/components/composer/NewRequest/OpenAPIEntryForm.jsx new file mode 100644 index 000000000..46ab87e85 --- /dev/null +++ b/src/client/components/composer/NewRequest/OpenAPIEntryForm.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +const OpenAPIEntryForm = ({ + warningMessage, + + newRequestsOpenAPI, +}) => { + const primaryServer = newRequestsOpenAPI?.openapiMetadata?.serverUrls[0]; + + const openAPILabel = 'OpenAPI'; + + return ( +
+ + + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default OpenAPIEntryForm; diff --git a/src/client/components/composer/NewRequest/OpenAPIMetadata.jsx b/src/client/components/composer/NewRequest/OpenAPIMetadata.jsx new file mode 100644 index 000000000..1b6e06991 --- /dev/null +++ b/src/client/components/composer/NewRequest/OpenAPIMetadata.jsx @@ -0,0 +1,38 @@ +import React from 'react'; + +function OpenAPIMetaData({ newRequestsOpenAPI }) { + return ( +
+
+
Metadata
+
+
+
Title
+ {newRequestsOpenAPI?.openapiMetadata?.info.title + ? newRequestsOpenAPI.openapiMetadata.info.title + : ''} +
+
+
Info
+ {newRequestsOpenAPI?.openapiMetadata?.info.description + ? newRequestsOpenAPI.openapiMetadata.info.description + : ''} +
+
+
Version
+ {newRequestsOpenAPI?.openapiMetadata?.info.version + ? newRequestsOpenAPI.openapiMetadata.info.version + : ''} +
+
+
OpenAPI
+ {newRequestsOpenAPI?.openapiMetadata?.info.openapi + ? newRequestsOpenAPI.openapiMetadata.info.openapi + : ''} +
+
+
+ ); +} + +export default OpenAPIMetaData; diff --git a/src/client/components/composer/NewRequest/OpenAPIServerForm.jsx b/src/client/components/composer/NewRequest/OpenAPIServerForm.jsx new file mode 100644 index 000000000..b78a580d8 --- /dev/null +++ b/src/client/components/composer/NewRequest/OpenAPIServerForm.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import ContentReqRowComposer from './ContentReqRowComposer'; + +export default function OpenAPIServerForm({ + newRequestsOpenAPI, + setNewRequestsOpenAPI, +}) { + const onChangeUpdateHeader = (id, field, value) => { + const serversDeepCopy = JSON.parse( + JSON.stringify(newRequestsOpenAPI.openapiMetadata.serverUrls) + ); + // find server to update + let indexToBeUpdated; + for (let i = 0; i < serversDeepCopy.length; i += 1) { + if (serversDeepCopy[i].id === id) { + indexToBeUpdated = i; + break; + } + } + + // update + serversDeepCopy[indexToBeUpdated][field] = value; + + // also switch checkbox if they are typing + if (field === 'key' || field === 'value') { + serversDeepCopy[indexToBeUpdated].active = true; + } + + setNewRequestsOpenAPI({ + serverUrls: serversDeepCopy, + }); + }; + + const serversArr = newRequestsOpenAPI?.openapiMetadata?.serverUrls?.map( + (server, index) => { + const contentTypeServer = { + id: Math.random() * 1000000, + active: true, + key: `Server ${index + 1}`, + value: server, + }; + return ( + + ); + } + ); + + return ( +
+
+
Servers
+ +
+
{serversArr && serversArr}
+
+ ); +} diff --git a/src/client/components/composer/NewRequest/ProtocolSelect.jsx b/src/client/components/composer/NewRequest/ProtocolSelect.jsx index 9777babf9..f909759f4 100644 --- a/src/client/components/composer/NewRequest/ProtocolSelect.jsx +++ b/src/client/components/composer/NewRequest/ProtocolSelect.jsx @@ -1,84 +1,110 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -const classNames = require('classnames'); - -class ProtocolSelect extends Component { - constructor(props) { - super(props); - } - - render() { - const HTTPSStyleClasses = classNames({ - composer_protocol_button: true, - http: true, - 'composer_protocol_button-selected_http': (this.props.currentProtocol === '' || /https?:\/\//.test(this.props.currentProtocol)) && !this.props.graphQL && !this.props.gRPC, - }); - const WSStyleClasses = classNames({ - composer_protocol_button: true, - ws: true, - 'composer_protocol_button-selected_ws': /wss?:\/\//.test(this.props.currentProtocol) && !this.props.graphQL, - }); - const GQLStyleClasses = classNames({ - composer_protocol_button: true, - gql: true, - 'composer_protocol_button-selected_gql': this.props.graphQL, - }); - const GRPCStyleClasses = classNames({ - composer_protocol_button: true, - grpc: true, - 'composer_protocol_button-selected_grpc': this.props.gRPC, - }); - - return ( -
-
this.props.onChangeHandler({ target: { value: 'http://' } }, 'protocol', 'rest') - } - > - REST -
-
this.props.onChangeHandler({ target: { value: 'ws://' } }, 'protocol', 'ws')} - > - WS -
-
this.props.onChangeHandler({ target: { value: '' } }, 'protocol', 'graphQL') - } - > - GRAPHQL -
-
this.props.onChangeHandler({ target: { value: '' } }, 'protocol', 'grpc') - } - > - GRPC -
-
- ); - } -} - -ProtocolSelect.propTypes = { - currentProtocol: PropTypes.string.isRequired, - onChangeHandler: PropTypes.func.isRequired, -}; - -export default ProtocolSelect; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +const classNames = require('classnames'); + +class ProtocolSelect extends Component { + constructor(props) { + super(props); + } + + render() { + const HTTPSStyleClasses = classNames({ + composer_protocol_button: true, + http: true, + 'composer_protocol_button-selected_http': + (this.props.currentProtocol === '' || + /https?:\/\//.test(this.props.currentProtocol)) && + !this.props.graphQL && + !this.props.gRPC, + }); + const WSStyleClasses = classNames({ + composer_protocol_button: true, + ws: true, + 'composer_protocol_button-selected_ws': + /wss?:\/\//.test(this.props.currentProtocol) && !this.props.graphQL, + }); + const GQLStyleClasses = classNames({ + composer_protocol_button: true, + gql: true, + 'composer_protocol_button-selected_gql': this.props.graphQL, + }); + const GRPCStyleClasses = classNames({ + composer_protocol_button: true, + grpc: true, + 'composer_protocol_button-selected_grpc': this.props.gRPC, + }); + + return ( +
+
+ this.props.onChangeHandler( + { target: { value: 'http://' } }, + 'protocol', + 'rest' + ) + } + > + REST +
+
+ this.props.onChangeHandler( + { target: { value: 'ws://' } }, + 'protocol', + 'ws' + ) + } + > + WS +
+
+ this.props.onChangeHandler( + { target: { value: '' } }, + 'protocol', + 'graphQL' + ) + } + > + GRAPHQL +
+
+ this.props.onChangeHandler( + { target: { value: '' } }, + 'protocol', + 'grpc' + ) + } + > + GRPC +
+
+ ); + } +} + +ProtocolSelect.propTypes = { + currentProtocol: PropTypes.string.isRequired, + onChangeHandler: PropTypes.func.isRequired, +}; + +export default ProtocolSelect; diff --git a/src/client/components/composer/NewRequest/RawBodyTypeSelect.jsx b/src/client/components/composer/NewRequest/RawBodyTypeSelect.jsx index bc4a688ad..f024347fa 100644 --- a/src/client/components/composer/NewRequest/RawBodyTypeSelect.jsx +++ b/src/client/components/composer/NewRequest/RawBodyTypeSelect.jsx @@ -1,175 +1,159 @@ -import React, { useState, useRef, useEffect } from "react"; -import PropTypes from "prop-types"; -import dropDownArrow from "../../../../assets/icons/caret-down.svg"; - -const classNames = require("classnames"); - -const RawBodyTypeSelect = (props) => { - const { - setNewRequestBody, - newRequestBody, - setNewRequestHeaders, - newRequestHeaders, - } = props; - - const [dropdownIsActive, setDropdownIsActive] = useState(); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - }; - document.addEventListener("click", closeDropdown); - return () => document.removeEventListener("click", closeDropdown); - }, []); - - // const removeContentTypeHeader = () => { - // const filtered = newRequestHeaders.headersArr.filter( - // (header) => header.key.toLowerCase() !== "content-type" - // ); - // setNewRequestHeaders({ - // headersArr: filtered, - // count: filtered.length, - // }); - // } - - const setNewRawBodyType = (rawTypeStr) => { - setNewRequestBody({ - ...newRequestBody, - rawType: rawTypeStr, - }); - const headersCopy = JSON.parse(JSON.stringify(newRequestHeaders)); - headersCopy.headersArr[0] = { - id: Math.random() * 1000000, - active: true, - key: "Content-type", - value: rawTypeStr, - }; - setNewRequestHeaders({ - headersArr: headersCopy.headersArr, - }); - }; - - return ( -
-
- -
- -
- -
-
- ); -}; - -// - -RawBodyTypeSelect.propTypes = { - newRequestBody: PropTypes.object.isRequired, - setNewRequestBody: PropTypes.func.isRequired, -}; - -export default RawBodyTypeSelect; +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState, useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import dropDownArrow from '../../../../assets/icons/caret-down.svg'; + +const RawBodyTypeSelect = (props) => { + const { + setNewRequestBody, + newRequestBody, + setNewRequestHeaders, + newRequestHeaders, + } = props; + + const [dropdownIsActive, setDropdownIsActive] = useState(); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + // const removeContentTypeHeader = () => { + // const filtered = newRequestHeaders.headersArr.filter( + // (header) => header.key.toLowerCase() !== "content-type" + // ); + // setNewRequestHeaders({ + // headersArr: filtered, + // count: filtered.length, + // }); + // } + + const setNewRawBodyType = (rawTypeStr) => { + setNewRequestBody({ + ...newRequestBody, + rawType: rawTypeStr, + }); + const headersCopy = JSON.parse(JSON.stringify(newRequestHeaders)); + headersCopy.headersArr[0] = { + id: Math.random() * 1000000, + active: true, + key: 'Content-type', + value: rawTypeStr, + }; + setNewRequestHeaders({ + headersArr: headersCopy.headersArr, + }); + }; + + return ( +
+
+ +
+ +
+ +
+
+ ); +}; + +RawBodyTypeSelect.propTypes = { + newRequestBody: PropTypes.object.isRequired, + setNewRequestBody: PropTypes.func.isRequired, +}; + +export default RawBodyTypeSelect; diff --git a/src/client/components/composer/NewRequest/RestMethodAndEndpointEntryForm.jsx b/src/client/components/composer/NewRequest/RestMethodAndEndpointEntryForm.jsx index d301286f7..0419b2f6a 100644 --- a/src/client/components/composer/NewRequest/RestMethodAndEndpointEntryForm.jsx +++ b/src/client/components/composer/NewRequest/RestMethodAndEndpointEntryForm.jsx @@ -1,158 +1,172 @@ -/* eslint-disable */ - -import React, { useState, useRef, Component, useEffect } from "react"; -import dropDownArrow from "../../../../assets/icons/arrow_drop_down_white_192x192.png"; -// import ProtocolSelect from "./ProtocolSelect.jsx"; - -const RestMethodAndEndpointEntryForm = ({ - warningMessage, - setComposerWarningMessage, - setNewRequestFields, - newRequestFields, - setNewRequestBody, - newRequestBody, - setNewTestContent, -}) => { - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(); - - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - } - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); - - const warningCheck = () => { - if (warningMessage.uri) { - const newWarningMessage = { ...warningMessage }; - delete warningMessage.uri; - setComposerWarningMessage({ ...newWarningMessage }); - } - } - - const methodChangeHandler = (newMethodStr) => { - warningCheck(); - //if one of 5 http methods (get, post, put, patch, delete) - setNewRequestBody({ - ...newRequestBody, - bodyType: "raw", - bodyContent: "", - }); - - //always set new method - setNewRequestFields({ - ...newRequestFields, - method: newMethodStr, - protocol: '', - }); - - setNewTestContent(""); - } - - - const urlChangeHandler = (e, network) => { - warningCheck(); - const url = e.target.value; - setNewRequestFields({ - ...newRequestFields, - restUrl: url, - url, - }); - }; - - - - return ( -
- {/* ************** RestMethodAndEndpointEntryForm ************** */} -
- -
- -
- - - - - { - urlChangeHandler(e, newRequestFields.network); - }} - /> -
- - {warningMessage.uri && ( -
{warningMessage.uri}
- )} -
- ); -}; - -export default RestMethodAndEndpointEntryForm; +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState, useRef, useEffect } from 'react'; +import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; +// import ProtocolSelect from "./ProtocolSelect.jsx"; + +const RestMethodAndEndpointEntryForm = ({ + warningMessage, + setComposerWarningMessage, + setNewRequestFields, + newRequestFields, + setNewRequestBody, + newRequestBody, + setNewTestContent, +}) => { + const [dropdownIsActive, setDropdownIsActive] = useState(false); + const dropdownEl = useRef(); + + useEffect(() => { + const closeDropdown = (event) => { + if (!dropdownEl.current.contains(event.target)) { + setDropdownIsActive(false); + } + }; + document.addEventListener('click', closeDropdown); + return () => document.removeEventListener('click', closeDropdown); + }, []); + + const warningCheck = () => { + if (warningMessage.uri) { + const newWarningMessage = { ...warningMessage }; + delete warningMessage.uri; + setComposerWarningMessage({ ...newWarningMessage }); + } + }; + + const methodChangeHandler = (newMethodStr) => { + warningCheck(); + //if one of 5 http methods (get, post, put, patch, delete) + setNewRequestBody({ + ...newRequestBody, + bodyType: 'raw', + bodyContent: '', + }); + + //always set new method + setNewRequestFields({ + ...newRequestFields, + method: newMethodStr, + protocol: '', + }); + + setNewTestContent(''); + }; + + const urlChangeHandler = (e, network) => { + warningCheck(); + const url = e.target.value; + setNewRequestFields({ + ...newRequestFields, + restUrl: url, + url, + }); + }; + + return ( +
+
+
+ +
+ + + + { + urlChangeHandler(e, newRequestFields.network); + }} + /> +
+ + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default RestMethodAndEndpointEntryForm; diff --git a/src/client/components/composer/NewRequest/TestEntryForm.jsx b/src/client/components/composer/NewRequest/TestEntryForm.jsx index 4d049b43f..d1bb2af09 100644 --- a/src/client/components/composer/NewRequest/TestEntryForm.jsx +++ b/src/client/components/composer/NewRequest/TestEntryForm.jsx @@ -1,58 +1,58 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from "react"; -import WWWForm from "./WWWForm.jsx"; -import BodyTypeSelect from "./BodyTypeSelect.jsx"; -import JSONTextArea from "./JSONTextArea.jsx"; -import RawBodyTypeSelect from "./RawBodyTypeSelect.jsx"; -import JSONPrettify from "./JSONPrettify.jsx"; -import TextCodeAreaEditable from "./TextCodeAreaEditable.jsx"; -import dropDownArrow from "../../../../assets/icons/caret-down-tests.svg"; -import dropDownArrowUp from "../../../../assets/icons/caret-up-tests.svg"; -import { isAbsolute, relative } from "path"; -import RestTestSnippetsContainer from "./TestSnippets/RestTestSnippetsContainer"; - -const TestEntryForm = (props) => { - const { testContent, setNewTestContent } = props; - - const [showTests, setShowTests] = useState(false); - const handleShowTests = () => setShowTests(!showTests); - - return ( -
- -
- {showTests === true && ( - <> - Hide Tests - - )} - {showTests === false && ( - <> - View Tests - - )} -
- {showTests === true && ( -
- { - setNewTestContent(value); - }} - /> -
- )} -
- ); -}; - -export default TestEntryForm; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; +import WWWForm from './WWWForm.jsx'; +import BodyTypeSelect from './BodyTypeSelect.jsx'; +import JSONTextArea from './JSONTextArea.jsx'; +import RawBodyTypeSelect from './RawBodyTypeSelect.jsx'; +import JSONPrettify from './JSONPrettify.jsx'; +import TextCodeAreaEditable from './TextCodeAreaEditable.jsx'; +import dropDownArrow from '../../../../assets/icons/caret-down-tests.svg'; +import dropDownArrowUp from '../../../../assets/icons/caret-up-tests.svg'; +import { isAbsolute, relative } from 'path'; +import RestTestSnippetsContainer from './TestSnippets/RestTestSnippetsContainer'; + +const TestEntryForm = (props) => { + const { testContent, setNewTestContent } = props; + + const [showTests, setShowTests] = useState(false); + const handleShowTests = () => setShowTests(!showTests); + + return ( +
+ +
+ {showTests === true && ( + <> + Hide Tests + + )} + {showTests === false && ( + <> + View Tests + + )} +
+ {showTests === true && ( +
+ { + setNewTestContent(value); + }} + /> +
+ )} +
+ ); +}; + +export default TestEntryForm; diff --git a/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippets.jsx b/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippets.jsx index 6a7311d20..11c451a4d 100644 --- a/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippets.jsx +++ b/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippets.jsx @@ -1,36 +1,36 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React from "react"; - -export default function RestTestSnippets(props) { - const { setNewTestContent, setShowTests } = props; - - const snippets = { - "Status code: Code is 200": - "assert.strictEqual(response.status, 200, 'response is 200')", - - "Access the cookies from the response object": - "assert.exists(response.cookies, 'cookies exists on response object')", - }; - - const handleClickOne = () => { - setShowTests(true); - setNewTestContent(snippets["Status code: Code is 200"]); - }; - - const handleClickTwo = () => { - setShowTests(true); - setNewTestContent(snippets["Access the cookies from the response object"]); - }; - - return ( -
- Status code: Code is 200; -
- - Access the cookies from the response object - - ; -
- ); -} +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; + +export default function RestTestSnippets(props) { + const { setNewTestContent, setShowTests } = props; + + const snippets = { + 'Status code: Code is 200': + "assert.strictEqual(response.status, 200, 'response is 200')", + + 'Access the cookies from the response object': + "assert.exists(response.cookies, 'cookies exists on response object')", + }; + + const handleClickOne = () => { + setShowTests(true); + setNewTestContent(snippets['Status code: Code is 200']); + }; + + const handleClickTwo = () => { + setShowTests(true); + setNewTestContent(snippets['Access the cookies from the response object']); + }; + + return ( +
+ Status code: Code is 200; +
+ + Access the cookies from the response object + + ; +
+ ); +} diff --git a/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippetsContainer.jsx b/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippetsContainer.jsx index c56dbf2b3..79260d109 100644 --- a/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippetsContainer.jsx +++ b/src/client/components/composer/NewRequest/TestSnippets/RestTestSnippetsContainer.jsx @@ -1,47 +1,47 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from "react"; -import dropDownArrow from "../../../../../assets/icons/caret-down-tests.svg"; - -import dropDownArrowUp from "../../../../../assets/icons/caret-up-tests.svg"; - -import RestTestSnippets from "./RestTestSnippets"; - -export default function RestTestSnippetsContainer(props) { - const { setShowTests, testContent, setNewTestContent } = props; - const [showSnippets, setShowSnippets] = useState(false); - - const handleShowSnippets = () => { - setShowSnippets(!showSnippets); - }; - return ( -
-
- {showSnippets === true && ( - <> - Test Snippets - - )} - - {showSnippets === false && ( - <> - Test Snippets - - )} -
- - {showSnippets === true && ( -
- -
- )} -
- ); -} +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; +import dropDownArrow from '../../../../../assets/icons/caret-down-tests.svg'; + +import dropDownArrowUp from '../../../../../assets/icons/caret-up-tests.svg'; + +import RestTestSnippets from './RestTestSnippets'; + +export default function RestTestSnippetsContainer(props) { + const { setShowTests, testContent, setNewTestContent } = props; + const [showSnippets, setShowSnippets] = useState(false); + + const handleShowSnippets = () => { + setShowSnippets(!showSnippets); + }; + return ( +
+
+ {showSnippets === true && ( + <> + Test Snippets + + )} + + {showSnippets === false && ( + <> + Test Snippets + + )} +
+ + {showSnippets === true && ( +
+ +
+ )} +
+ ); +} diff --git a/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippets.jsx b/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippets.jsx index 70e693294..bf1b1eb76 100644 --- a/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippets.jsx +++ b/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippets.jsx @@ -1,23 +1,23 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React from "react"; - -export default function WebsocketTestSnippets(props) { - const { setNewTestContent, setShowTests } = props; - - const snippets = { - "Status code: connection is open": - "assert.strictEqual(response.connection, 'open', 'response is open')", - }; - - const handleClickOne = () => { - setShowTests(true); - setNewTestContent(snippets["Status code: connection is open"]); - }; - - return ( -
- Status code: connection is open; -
- ); -} +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; + +export default function WebsocketTestSnippets(props) { + const { setNewTestContent, setShowTests } = props; + + const snippets = { + 'Status code: connection is open': + "assert.strictEqual(response.connection, 'open', 'response is open')", + }; + + const handleClickOne = () => { + setShowTests(true); + setNewTestContent(snippets['Status code: connection is open']); + }; + + return ( +
+ Status code: connection is open; +
+ ); +} diff --git a/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippetsContainer.jsx b/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippetsContainer.jsx index f8aaf16e2..ccb5bac39 100644 --- a/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippetsContainer.jsx +++ b/src/client/components/composer/NewRequest/TestSnippets/WebsocketTestSnippetsContainer.jsx @@ -1,47 +1,47 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from "react"; -import dropDownArrow from "../../../../../assets/icons/caret-down-tests.svg"; - -import dropDownArrowUp from "../../../../../assets/icons/caret-up-tests.svg"; - -import WebsocketTestSnippets from "./WebsocketTestSnippets"; - -export default function WebsocketTestSnippetsContainer(props) { - const { setShowTests, testContent, setNewTestContent } = props; - const [showSnippets, setShowSnippets] = useState(false); - - const handleShowSnippets = () => { - setShowSnippets(!showSnippets); - }; - return ( -
-
- {showSnippets === true && ( - <> - Test Snippets - - )} - - {showSnippets === false && ( - <> - Test Snippets - - )} -
- - {showSnippets === true && ( -
- -
- )} -
- ); -} +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; +import dropDownArrow from '../../../../../assets/icons/caret-down-tests.svg'; + +import dropDownArrowUp from '../../../../../assets/icons/caret-up-tests.svg'; + +import WebsocketTestSnippets from './WebsocketTestSnippets'; + +export default function WebsocketTestSnippetsContainer(props) { + const { setShowTests, testContent, setNewTestContent } = props; + const [showSnippets, setShowSnippets] = useState(false); + + const handleShowSnippets = () => { + setShowSnippets(!showSnippets); + }; + return ( +
+
+ {showSnippets === true && ( + <> + Test Snippets + + )} + + {showSnippets === false && ( + <> + Test Snippets + + )} +
+ + {showSnippets === true && ( +
+ +
+ )} +
+ ); +} diff --git a/src/client/components/composer/NewRequest/TextCodeAreaEditable.jsx b/src/client/components/composer/NewRequest/TextCodeAreaEditable.jsx index b5d830bc9..4aeaa79c0 100644 --- a/src/client/components/composer/NewRequest/TextCodeAreaEditable.jsx +++ b/src/client/components/composer/NewRequest/TextCodeAreaEditable.jsx @@ -1,25 +1,25 @@ -import { POINT_CONVERSION_UNCOMPRESSED } from "constants"; -import React, { useState, useEffect, useRef } from "react"; -import { UnControlled as CodeMirror } from "react-codemirror2"; -import "codemirror/theme/neat.css"; - -export default function TextCodeAreaEditable({ value, mode, onChange, theme }) { - return ( -
- -
- ); -} +import { POINT_CONVERSION_UNCOMPRESSED } from 'constants'; +import React from 'react'; +import { UnControlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/theme/neat.css'; + +export default function TextCodeAreaEditable({ value, mode, onChange, theme }) { + return ( +
+ +
+ ); +} diff --git a/src/client/components/composer/NewRequest/TextCodeAreaReadOnly.jsx b/src/client/components/composer/NewRequest/TextCodeAreaReadOnly.jsx index bcfa239d8..444de9975 100644 --- a/src/client/components/composer/NewRequest/TextCodeAreaReadOnly.jsx +++ b/src/client/components/composer/NewRequest/TextCodeAreaReadOnly.jsx @@ -1,23 +1,21 @@ -import React from 'react'; -import {UnControlled as CodeMirror} from 'react-codemirror2'; - - -export default function TextCodeAreaEditable ({ value, mode, onChange, theme }) { - - return ( -
- -
- ); -} \ No newline at end of file +import React from 'react'; +import { UnControlled as CodeMirror } from 'react-codemirror2'; + +export default function TextCodeAreaEditable({ value, mode, onChange, theme }) { + return ( +
+ +
+ ); +} diff --git a/src/client/components/composer/NewRequest/WSEndpointEntryForm.jsx b/src/client/components/composer/NewRequest/WSEndpointEntryForm.jsx index d46935417..3b877d977 100644 --- a/src/client/components/composer/NewRequest/WSEndpointEntryForm.jsx +++ b/src/client/components/composer/NewRequest/WSEndpointEntryForm.jsx @@ -1,50 +1,48 @@ -/* eslint-disable default-case */ -import React from "react"; - -const WSEndpointEntryForm = ({ - warningMessage, - setComposerWarningMessage, - setNewRequestFields, - newRequestFields, -}) => { - - const warningCheck = () => { - if (warningMessage.uri) { - const warningMessage = { ...warningMessage }; - delete warningMessage.uri; - setComposerWarningMessage({ ...warningMessage }); - } - } - - const urlChangeHandler = (e) => { - warningCheck(); - const url = e.target.value; - setNewRequestFields({ - ...newRequestFields, - wsUrl: url, - url, - }); - } - - return ( -
-
- WS -
- { - urlChangeHandler(e); - }} - /> - {warningMessage.uri && ( -
{warningMessage.uri}
- )} -
- ); -}; - -export default WSEndpointEntryForm; +import React from 'react'; + +const WSEndpointEntryForm = ({ + warningMessage, + setComposerWarningMessage, + setNewRequestFields, + newRequestFields, +}) => { + const warningCheck = () => { + if (warningMessage.uri) { + const warningMessage = { ...warningMessage }; + delete warningMessage.uri; + setComposerWarningMessage({ ...warningMessage }); + } + }; + + const urlChangeHandler = (e) => { + warningCheck(); + const url = e.target.value; + setNewRequestFields({ + ...newRequestFields, + wsUrl: url, + url, + }); + }; + + return ( +
+
+ WS +
+ { + urlChangeHandler(e); + }} + /> + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default WSEndpointEntryForm; diff --git a/src/client/components/composer/NewRequest/WSTestEntryForm.jsx b/src/client/components/composer/NewRequest/WSTestEntryForm.jsx index 2ea3ceb40..186c395ca 100644 --- a/src/client/components/composer/NewRequest/WSTestEntryForm.jsx +++ b/src/client/components/composer/NewRequest/WSTestEntryForm.jsx @@ -1,58 +1,50 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from "react"; -import WWWForm from "./WWWForm.jsx"; -import BodyTypeSelect from "./BodyTypeSelect.jsx"; -import JSONTextArea from "./JSONTextArea.jsx"; -import RawBodyTypeSelect from "./RawBodyTypeSelect.jsx"; -import JSONPrettify from "./JSONPrettify.jsx"; -import TextCodeAreaEditable from "./TextCodeAreaEditable.jsx"; -import dropDownArrow from "../../../../assets/icons/caret-down-tests.svg"; -import dropDownArrowUp from "../../../../assets/icons/caret-up-tests.svg"; -import { isAbsolute, relative } from "path"; -import WebsocketTestSnippetsContainer from "./TestSnippets/WebsocketTestSnippetsContainer"; - -const WSTestEntryForm = (props) => { - const { testContent, setNewTestContent } = props; - - const [showTests, setShowTests] = useState(false); - const handleShowTests = () => setShowTests(!showTests); - - return ( -
- -
- {showTests === true && ( - <> - Hide Tests - - )} - {showTests === false && ( - <> - View Tests - - )} -
- {showTests === true && ( -
- { - setNewTestContent(value); - }} - /> -
- )} -
- ); -}; - -export default WSTestEntryForm; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; +import TextCodeAreaEditable from './TextCodeAreaEditable.jsx'; +import WebsocketTestSnippetsContainer from './TestSnippets/WebsocketTestSnippetsContainer'; + +const WSTestEntryForm = (props) => { + const { testContent, setNewTestContent } = props; + + const [showTests, setShowTests] = useState(false); + const handleShowTests = () => setShowTests(!showTests); + + return ( +
+ +
+ {showTests === true && ( + <> + Hide Tests + + )} + {showTests === false && ( + <> + View Tests + + )} +
+ {showTests === true && ( +
+ { + setNewTestContent(value); + }} + /> +
+ )} +
+ ); +}; + +export default WSTestEntryForm; diff --git a/src/client/components/composer/NewRequest/WWWForm.jsx b/src/client/components/composer/NewRequest/WWWForm.jsx index 5b7df5c14..19a68b888 100644 --- a/src/client/components/composer/NewRequest/WWWForm.jsx +++ b/src/client/components/composer/NewRequest/WWWForm.jsx @@ -1,205 +1,212 @@ -/* eslint-disable no-param-reassign */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ContentReqRowComposer from './ContentReqRowComposer.jsx'; - -class WWWForm extends Component { - constructor(props) { - super(props); - this.state = { - wwwFields: [], - rawString: '', - } - this.updateWwwField = this.updateWwwField.bind(this); - this.deleteWwwField = this.deleteWwwField.bind(this) - } - - createWWWClone() { - return JSON.parse(JSON.stringify(this.state.wwwFields)) - } - - checkOldBody() { - //create state from the incoming string, - - //if there is only one k/v pair... - if (!this.props.newRequestBody.bodyContent.includes('&')) { - let key = this.props.newRequestBody.bodyContent.split('=')[0]; - let value = this.props.newRequestBody.bodyContent.split('=')[1]; - this.setState({ - wwwFields: [{ - id: `id${this.state.wwwFields.length}`, - active: true, - key, - value, - }], - rawString: this.props.newRequestBody.bodyContent - }, () => { - this.addFieldIfNeeded(); - }) - } - //more than one k/v pair - else if (this.props.newRequestBody.bodyContent.includes('&')) { - let fields = this.props.newRequestBody.bodyContent.split('&') - .map(field => { - let key = field.split('=')[0]; - let value = field.split('=')[1]; - return { - id: `id${this.state.wwwFields.length}`, - active: true, - key, - value, - } - }) - .filter(field => field.key !== '' || field.value !== ''); - - this.setState({ - wwwFields: fields, - rawString: this.props.newRequestBody.bodyContent - }) - } - } - - - componentDidMount() { - //"hi"="rocky"&"meow"="cats" in the body turns into 2 key/value pairs when switching to x-www - let matches = this.props.newRequestBody.bodyContent.match(/(([^(&|\n)]+=[^(&|\n)]+)&?)+/g); - if (matches) { - this.props.setNewRequestBody({ - ...this.props.newRequestBody, - bodyContent: matches.join(''), - }); - } else { - this.props.setNewRequestBody({ - ...this.props.newRequestBody, - bodyContent: '', - }); - } - this.addFieldIfNeeded(); - } - - - - componentDidUpdate() { - if (this.props.newRequestBody.bodyContent !== this.state.rawString) { - this.checkOldBody(); - } - const wwwFieldsDeepCopy = this.createWWWClone(); - if (wwwFieldsDeepCopy.length === 0 || wwwFieldsDeepCopy[wwwFieldsDeepCopy.length-1]?.key !== "") { - this.addWwwField() - } - } - - addWwwField() { - const wwwFieldsDeepCopy = this.createWWWClone(); - wwwFieldsDeepCopy.push({ - id: `id${this.state.wwwFields.length}`, - active: false, - key: '', - value: '' - }) - this.setState({ - wwwFields: wwwFieldsDeepCopy - }); - } - - updateWwwField(id, field, value) { - - const wwwFieldsDeepCopy = this.createWWWClone(); - - //find www to update - let indexToBeUpdated; - for (let i = 0; i < wwwFieldsDeepCopy.length; i++) { - if (wwwFieldsDeepCopy[i].id === id) { - indexToBeUpdated = i; - break; - } - } - const target = wwwFieldsDeepCopy[indexToBeUpdated] - - //update - target[field] = value; - - //also switch checkbox if they are typing - if (field === 'key' || field === 'value') { - target.active = true; - this.addWwwField(); - } - - this.setState({ - wwwFields: wwwFieldsDeepCopy, - }); - - } - - addFieldIfNeeded() { - if (this.isWwwFieldsEmpty()) { - const wwwFieldsDeepCopy = this.createWWWClone(); - - wwwFieldsDeepCopy.push({ - id: `id${this.state.wwwFields.length}`, - active: false, - key: '', - value: '', - }); - - this.setState({ - wwwFields: wwwFieldsDeepCopy, - }); - } - } - - isWwwFieldsEmpty() { - if (this.state.wwwFields.length === 0) { - return true; - } - - return this.state.wwwFields - .map(wwwField => (wwwField.key === '' && wwwField.value === '' ? 1 : 0)) - .reduce((acc, cur) => acc + cur) === 0; - } - - deleteWwwField(index) { - const newFields = this.state.wwwFields.slice(); - newFields.splice(index, 1); - if (!newFields.length) { - newFields.push({ - id: `id${this.state.wwwFields.length}`, - active: false, - key: '', - value: '', - }); - } - this.setState({ - wwwFields: newFields, - }) - } - - render() { - let wwwFieldsReactArr = this.state.wwwFields.map((wwwField, index) => { - return ( - - ) - }) - - return ( -
- {wwwFieldsReactArr} -
- ); - } -} - -WWWForm.propTypes = { - newRequestBody: PropTypes.object.isRequired, - setNewRequestBody: PropTypes.func.isRequired, -}; - -export default WWWForm; +/* eslint-disable react/sort-comp */ +/* eslint-disable no-param-reassign */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ContentReqRowComposer from './ContentReqRowComposer.jsx'; + +class WWWForm extends Component { + constructor(props) { + super(props); + this.state = { + wwwFields: [], + rawString: '', + }; + this.updateWwwField = this.updateWwwField.bind(this); + this.deleteWwwField = this.deleteWwwField.bind(this); + } + + createWWWClone() { + return JSON.parse(JSON.stringify(this.state.wwwFields)); + } + + checkOldBody() { + //create state from the incoming string, + + //if there is only one k/v pair... + if (!this.props.newRequestBody.bodyContent.includes('&')) { + const key = this.props.newRequestBody.bodyContent.split('=')[0]; + const value = this.props.newRequestBody.bodyContent.split('=')[1]; + this.setState( + { + wwwFields: [ + { + id: `id${this.state.wwwFields.length}`, + active: true, + key, + value, + }, + ], + rawString: this.props.newRequestBody.bodyContent, + }, + () => { + this.addFieldIfNeeded(); + } + ); + } + //more than one k/v pair + else if (this.props.newRequestBody.bodyContent.includes('&')) { + const fields = this.props.newRequestBody.bodyContent + .split('&') + .map((field) => { + const key = field.split('=')[0]; + const value = field.split('=')[1]; + return { + id: `id${this.state.wwwFields.length}`, + active: true, + key, + value, + }; + }) + .filter((field) => field.key !== '' || field.value !== ''); + + this.setState({ + wwwFields: fields, + rawString: this.props.newRequestBody.bodyContent, + }); + } + } + + componentDidMount() { + //"hi"="rocky"&"meow"="cats" in the body turns into 2 key/value pairs when switching to x-www + const matches = this.props.newRequestBody.bodyContent.match( + /(([^(&|\n)]+=[^(&|\n)]+)&?)+/g + ); + if (matches) { + this.props.setNewRequestBody({ + ...this.props.newRequestBody, + bodyContent: matches.join(''), + }); + } else { + this.props.setNewRequestBody({ + ...this.props.newRequestBody, + bodyContent: '', + }); + } + this.addFieldIfNeeded(); + } + + componentDidUpdate() { + if (this.props.newRequestBody.bodyContent !== this.state.rawString) { + this.checkOldBody(); + } + const wwwFieldsDeepCopy = this.createWWWClone(); + if ( + wwwFieldsDeepCopy.length === 0 || + wwwFieldsDeepCopy[wwwFieldsDeepCopy.length - 1]?.key !== '' + ) { + this.addWwwField(); + } + } + + addWwwField() { + const wwwFieldsDeepCopy = this.createWWWClone(); + wwwFieldsDeepCopy.push({ + id: `id${this.state.wwwFields.length}`, + active: false, + key: '', + value: '', + }); + this.setState({ + wwwFields: wwwFieldsDeepCopy, + }); + } + + updateWwwField(id, field, value) { + const wwwFieldsDeepCopy = this.createWWWClone(); + + //find www to update + let indexToBeUpdated; + for (let i = 0; i < wwwFieldsDeepCopy.length; i++) { + if (wwwFieldsDeepCopy[i].id === id) { + indexToBeUpdated = i; + break; + } + } + const target = wwwFieldsDeepCopy[indexToBeUpdated]; + + //update + target[field] = value; + + //also switch checkbox if they are typing + if (field === 'key' || field === 'value') { + target.active = true; + this.addWwwField(); + } + + this.setState({ + wwwFields: wwwFieldsDeepCopy, + }); + } + + addFieldIfNeeded() { + if (this.isWwwFieldsEmpty()) { + const wwwFieldsDeepCopy = this.createWWWClone(); + + wwwFieldsDeepCopy.push({ + id: `id${this.state.wwwFields.length}`, + active: false, + key: '', + value: '', + }); + + this.setState({ + wwwFields: wwwFieldsDeepCopy, + }); + } + } + + isWwwFieldsEmpty() { + if (this.state.wwwFields.length === 0) { + return true; + } + + return ( + this.state.wwwFields + .map((wwwField) => + wwwField.key === '' && wwwField.value === '' ? 1 : 0 + ) + .reduce((acc, cur) => acc + cur) === 0 + ); + } + + deleteWwwField(index) { + const newFields = this.state.wwwFields.slice(); + newFields.splice(index, 1); + if (!newFields.length) { + newFields.push({ + id: `id${this.state.wwwFields.length}`, + active: false, + key: '', + value: '', + }); + } + this.setState({ + wwwFields: newFields, + }); + } + + render() { + const wwwFieldsReactArr = this.state.wwwFields.map((wwwField, index) => { + return ( + + ); + }); + + return ( +
{wwwFieldsReactArr}
+ ); + } +} + +WWWForm.propTypes = { + newRequestBody: PropTypes.object.isRequired, + setNewRequestBody: PropTypes.func.isRequired, +}; + +export default WWWForm; diff --git a/src/client/components/composer/NewRequest/WebRTCServerEntryForm.jsx b/src/client/components/composer/NewRequest/WebRTCServerEntryForm.jsx new file mode 100644 index 000000000..8b777c9d2 --- /dev/null +++ b/src/client/components/composer/NewRequest/WebRTCServerEntryForm.jsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/addon/edit/matchbrackets'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/theme/twilight.css'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/show-hint.css'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/mode'; +import 'codemirror/addon/lint/lint.css'; + +const jBeautify = require('js-beautify').js; + +const WebRTCServerEntryForm = (props) => { + const { + newRequestBody: { bodyContent }, + newRequestBody: { bodyIsNew }, + + warningMessage, + } = props; + + const [cmValue, setValue] = useState(''); + + useEffect(() => { + if (!bodyIsNew) + setValue( + jBeautify(JSON.stringify(bodyContent.iceConfiguration.iceServers)) + ); + }, [bodyContent, bodyIsNew]); + + return ( +
+ {warningMessage ?
{warningMessage.body}
: null} +
TURN or STUN Servers
+
+ { + editor.setSize('100%', '100%'); + }} + /> +
+
+ ); +}; + +export default WebRTCServerEntryForm; diff --git a/src/client/components/composer/NewRequest/WebRTCSessionEntryForm.jsx b/src/client/components/composer/NewRequest/WebRTCSessionEntryForm.jsx new file mode 100644 index 000000000..6c8505c02 --- /dev/null +++ b/src/client/components/composer/NewRequest/WebRTCSessionEntryForm.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const WebRTCSessionEntryForm = ({ warningMessage }) => { + return ( +
+
+ SDP +
+ + {warningMessage.uri && ( +
{warningMessage.uri}
+ )} +
+ ); +}; + +export default WebRTCSessionEntryForm; diff --git a/src/client/components/composer/OpenAPIContainer.jsx b/src/client/components/composer/OpenAPIContainer.jsx new file mode 100644 index 000000000..4ce1e6dba --- /dev/null +++ b/src/client/components/composer/OpenAPIContainer.jsx @@ -0,0 +1,156 @@ +import React from 'react'; +import uuid from 'uuid/v4'; +import historyController from '../../controllers/historyController'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; +import OpenAPIEntryForm from './NewRequest/OpenAPIEntryForm'; +import OpenAPIDocumentEntryForm from './NewRequest/OpenAPIDocumentEntryForm.jsx'; +import OpenAPIMetadata from './NewRequest/OpenAPIMetadata.jsx'; +import OpenAPIServerForm from './NewRequest/OpenAPIServerForm.jsx'; + +function OpenAPIContainer({ + resetComposerFields, + setNewRequestsOpenAPI, + newRequestsOpenAPI, + setNewRequestFields, + newRequestFields, + newRequestFields: { + gRPC, + webrtc, + graphQL, + restUrl, + wsUrl, + gqlUrl, + grpcUrl, + network, + testContent, + }, + setNewRequestBody, + newRequestBody, + newRequestBody: { rawType, bodyType }, + setNewRequestHeaders, + newRequestHeaders, + newRequestHeaders: { headersArr }, + setNewRequestCookies, + currentTab, + setComposerWarningMessage, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const requestValidationCheck = () => { + const validationMessage = {}; + //Error conditions removing the need for url for now + return validationMessage; + }; + + const addNewRequest = () => { + const warnings = requestValidationCheck(); + if (Object.keys(warnings).length > 0) { + setComposerWarningMessage(warnings); + return; + } + + newRequestsOpenAPI.openapiReqArray.forEach((req) => { + const reqRes = { + id: uuid(), + created_at: new Date(), + host: `${newRequestsOpenAPI.openapiMetadata.serverUrls[0]}`, + protocol: 'https://', + url: `${newRequestsOpenAPI.openapiMetadata.serverUrls[0]}${req.endpoint}`, + graphQL, + gRPC, + webrtc, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + request: { + method: req.method, + headers: headersArr.filter((header) => header.active && !!header.key), + body: req.body, + bodyType, + rawType, + network, + restUrl, + wsUrl, + gqlUrl, + testContent: testContent || '', + grpcUrl, + }, + response: { + cookies: [], + headers: {}, + stream: null, + events: [], + }, + checked: false, + minimized: false, + tab: currentTab, + }; + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + + setNewRequestBody({ + ...newRequestBody, + bodyType: '', + rawType: '', + }); + setNewRequestFields({ + ...newRequestFields, + url: `${newRequestsOpenAPI.openapiMetadata.serverUrls[0]}${req.endpoint}`, + restUrl, + }); + }); + + setWorkspaceActiveTab('workspace'); + }; + + return ( +
+
+ + + + + +
+
+ +
+
+ ); +} + +export default OpenAPIContainer; diff --git a/src/client/components/composer/RestContainer.jsx b/src/client/components/composer/RestContainer.jsx index 5ff050c4e..6363313e1 100644 --- a/src/client/components/composer/RestContainer.jsx +++ b/src/client/components/composer/RestContainer.jsx @@ -1,226 +1,218 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ -/* eslint-disable jsx-a11y/label-has-for */ -import React from "react"; -import uuid from "uuid/v4"; // (Universally Unique Identifier)--generates a unique ID -import historyController from "../../controllers/historyController"; -import HeaderEntryForm from "./NewRequest/HeaderEntryForm"; -import BodyEntryForm from "./NewRequest/BodyEntryForm.jsx"; -import TestEntryForm from "./NewRequest/TestEntryForm.jsx"; -import CookieEntryForm from "./NewRequest/CookieEntryForm.jsx"; -import RestMethodAndEndpointEntryForm from "./NewRequest/RestMethodAndEndpointEntryForm.jsx"; -import NewRequestButton from "./NewRequest/NewRequestButton.jsx"; - -export default function RestContainer({ - resetComposerFields, - setNewRequestFields, - newRequestFields, - newRequestFields: { - gRPC, - url, - method, - protocol, - graphQL, - restUrl, - wsUrl, - gqlUrl, - grpcUrl, - network, - testContent, - }, - setNewRequestBody, - setNewTestContent, - newRequestBody, - newRequestBody: { - JSONFormatted, - rawType, - bodyContent, - bodyVariables, - bodyType, - }, - setNewRequestHeaders, - newRequestHeaders, - newRequestHeaders: { headersArr }, - setNewRequestCookies, - newRequestCookies, - newRequestCookies: { cookiesArr }, - setNewRequestStreams, - newRequestStreams, - newRequestStreams: { - selectedService, - selectedRequest, - selectedPackage, - streamingType, - initialQuery, - streamsArr, - streamContent, - services, - protoPath, - protoContent, - }, - setNewRequestSSE, - newRequestSSE: { isSSE }, - currentTab, - introspectionData, - setComposerWarningMessage, - setComposerDisplay, - warningMessage, - reqResAdd, - setWorkspaceActiveTab, -}) { - const requestValidationCheck = () => { - const validationMessage = {}; - //Error conditions... - if (/https?:\/\/$|wss?:\/\/$/.test(url)) { - //if url is only http/https/ws/wss:// - validationMessage.uri = "Enter a valid URI"; - } - if (!/(https?:\/\/)|(wss?:\/\/)/.test(url)) { - //if url doesn't have http/https/ws/wss:// - validationMessage.uri = "Enter a valid URI"; - } - if (!JSONFormatted && rawType === "application/json") { - validationMessage.json = "Please fix JSON body formatting errors"; - } - return validationMessage; - }; - - const addNewRequest = () => { - const warnings = requestValidationCheck(); - if (Object.keys(warnings).length > 0) { - setComposerWarningMessage(warnings); - return; - } - - let reqRes; - const protocol = url.match(/(https?:\/\/)|(wss?:\/\/)/)[0]; - // HTTP && GRAPHQL QUERY & MUTATION REQUESTS - if (!/wss?:\/\//.test(protocol) && !gRPC) { - const URIWithoutProtocol = `${url.split(protocol)[1]}/`; - URIWithoutProtocol; - const host = protocol + URIWithoutProtocol.split("/")[0]; - let path = `/${URIWithoutProtocol.split("/") - .splice(1) - .join("/") - .replace(/\/{2,}/g, "/")}`; - if (path.charAt(path.length - 1) === "/" && path.length > 1) { - path = path.substring(0, path.length - 1); - } - path = path.replace(/https?:\//g, "http://"); - reqRes = { - id: uuid(), - created_at: new Date(), - protocol: url.match(/https?:\/\//)[0], - host, - path, - url, - graphQL, - gRPC, - timeSent: null, - timeReceived: null, - connection: "uninitialized", - connectionType: null, - checkSelected: false, - protoPath, - request: { - method, - headers: headersArr.filter((header) => header.active && !!header.key), - cookies: cookiesArr.filter((cookie) => cookie.active && !!cookie.key), - body: bodyContent || "", - bodyType, - bodyVariables: bodyVariables || "", - rawType, - isSSE, - network, - restUrl, - testContent: testContent || "", - wsUrl, - gqlUrl, - grpcUrl, - }, - response: { - headers: null, - events: null, - }, - checked: false, - minimized: false, - tab: currentTab, - }; - } - - // add request to history - historyController.addHistoryToIndexedDb(reqRes); - reqResAdd(reqRes); - - //reset for next request - resetComposerFields(); - setWorkspaceActiveTab("workspace"); - }; - - const handleSSEPayload = (e) => { - setNewRequestSSE(e.target.checked); - }; - - return ( -
-
- - - - {/* SSE TOGGLE SWITCH */} -
- - Server Sent Events - - { - handleSSEPayload(e); - }} - checked={isSSE} - /> -
- {method !== "GET" && ( - - )} - -
-
- -
-
- ); -} +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/label-has-for */ +import React from 'react'; +import uuid from 'uuid/v4'; +import historyController from '../../controllers/historyController'; +import HeaderEntryForm from './NewRequest/HeaderEntryForm'; +import BodyEntryForm from './NewRequest/BodyEntryForm.jsx'; +import TestEntryForm from './NewRequest/TestEntryForm.jsx'; +import CookieEntryForm from './NewRequest/CookieEntryForm.jsx'; +import RestMethodAndEndpointEntryForm from './NewRequest/RestMethodAndEndpointEntryForm.jsx'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; + +function RestContainer({ + resetComposerFields, + setNewRequestFields, + newRequestFields, + newRequestFields: { + gRPC, + url, + method, + graphQL, + restUrl, + wsUrl, + webrtc, + gqlUrl, + grpcUrl, + network, + testContent, + }, + setNewRequestBody, + setNewTestContent, + newRequestBody, + newRequestBody: { + JSONFormatted, + rawType, + bodyContent, + bodyVariables, + bodyType, + }, + setNewRequestHeaders, + newRequestHeaders, + newRequestHeaders: { headersArr }, + setNewRequestCookies, + newRequestCookies, + newRequestCookies: { cookiesArr }, + setNewRequestStreams, + newRequestStreams, + newRequestStreams: { protoPath }, + setNewRequestSSE, + newRequestSSE: { isSSE }, + currentTab, + introspectionData, + setComposerWarningMessage, + setComposerDisplay, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const requestValidationCheck = () => { + const validationMessage = {}; + //Error conditions... + if (/https?:\/\/$|wss?:\/\/$/.test(url)) { + //if url is only http/https/ws/wss:// + validationMessage.uri = 'Enter a valid URI'; + } + if (!/(https?:\/\/)|(wss?:\/\/)/.test(url)) { + //if url doesn't have http/https/ws/wss:// + validationMessage.uri = 'Enter a valid URI'; + } + if (!JSONFormatted && rawType === 'application/json') { + validationMessage.json = 'Please fix JSON body formatting errors'; + } + return validationMessage; + }; + + const addNewRequest = () => { + const warnings = requestValidationCheck(); + if (Object.keys(warnings).length > 0) { + setComposerWarningMessage(warnings); + return; + } + + let reqRes; + const protocol = url.match(/(https?:\/\/)|(wss?:\/\/)/)[0]; + // HTTP && GRAPHQL QUERY & MUTATION REQUESTS + if (!/wss?:\/\//.test(protocol) && !gRPC) { + const URIWithoutProtocol = `${url.split(protocol)[1]}/`; + URIWithoutProtocol; + const host = protocol + URIWithoutProtocol.split('/')[0]; + let path = `/${URIWithoutProtocol.split('/') + .splice(1) + .join('/') + .replace(/\/{2,}/g, '/')}`; + if (path.charAt(path.length - 1) === '/' && path.length > 1) { + path = path.substring(0, path.length - 1); + } + path = path.replace(/https?:\//g, 'http://'); + reqRes = { + id: uuid(), + created_at: new Date(), + protocol: url.match(/https?:\/\//)[0], + host, + path, + url, + webrtc, + graphQL, + gRPC, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + protoPath, + request: { + method, + headers: headersArr.filter((header) => header.active && !!header.key), + cookies: cookiesArr.filter((cookie) => cookie.active && !!cookie.key), + body: bodyContent || '', + bodyType, + bodyVariables: bodyVariables || '', + rawType, + isSSE, + network, + restUrl, + testContent: testContent || '', + wsUrl, + gqlUrl, + grpcUrl, + }, + response: { + headers: null, + events: null, + }, + checked: false, + minimized: false, + tab: currentTab, + }; + } + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + setWorkspaceActiveTab('workspace'); + }; + + const handleSSEPayload = (e) => { + setNewRequestSSE(e.target.checked); + }; + + return ( +
+
+ + + + {/* SSE TOGGLE SWITCH */} +
+ + Server Sent Events + + { + handleSSEPayload(e); + }} + checked={isSSE} + /> +
+ {method !== 'GET' && ( + + )} + +
+
+ +
+
+ ); +} + +export default RestContainer; diff --git a/src/client/components/composer/WSContainer.jsx b/src/client/components/composer/WSContainer.jsx index 5510f7c00..c2e32b1ee 100644 --- a/src/client/components/composer/WSContainer.jsx +++ b/src/client/components/composer/WSContainer.jsx @@ -1,123 +1,120 @@ -import React from "react"; -import uuid from "uuid/v4"; // (Universally Unique Identifier)--generates a unique ID -import historyController from "../../controllers/historyController"; -import WSEndpointEntryForm from "./NewRequest/WSEndpointEntryForm"; -import NewRequestButton from "./NewRequest/NewRequestButton.jsx"; -import WSTestEntryForm from "./NewRequest/WSTestEntryForm.jsx"; - -export default function WSContainer({ - setNewTestContent, - resetComposerFields, - setNewRequestFields, - newRequestFields, - newRequestFields: { - gRPC, - url, - method, - protocol, - graphQL, - restUrl, - wsUrl, - gqlUrl, - grpcUrl, - network, - testContent, - }, - setNewRequestSSE, - currentTab, - setComposerWarningMessage, - warningMessage, - reqResAdd, - setWorkspaceActiveTab, -}) { - const requestValidationCheck = () => { - const validationMessage = {}; - //Error conditions... - // if url is only http/https/ws/wss:// - // OR if url doesn't contain http/https/ws/wss - if ( - /https?:\/\/$|wss?:\/\/$/.test(url) || - !/(https?:\/\/)|(wss?:\/\/)/.test(url) - ) { - validationMessage.uri = "Enter a valid URI"; - } - return validationMessage; - }; - - const addNewRequest = () => { - const warnings = requestValidationCheck(); - if (Object.keys(warnings).length > 0) { - setComposerWarningMessage(warnings); - return; - } - - const protocol = url.match(/(https?:\/\/)|(wss?:\/\/)/)[0]; - - const reqRes = { - id: uuid(), - created_at: new Date(), - protocol: url.match(/wss?:\/\//)[0], - url, - timeSent: null, - timeReceived: null, - connection: "uninitialized", - connectionType: "WebSocket", - checkSelected: false, - request: { - method: "WS", - messages: [], - network, - restUrl, - wsUrl, - gqlUrl, - grpcUrl, - testContent, - }, - response: { - messages: [], - }, - checked: false, - tab: currentTab, - }; - - // add request to history - historyController.addHistoryToIndexedDb(reqRes); - reqResAdd(reqRes); - - //reset for next request - resetComposerFields(); - - setNewRequestFields({ - ...newRequestFields, - protocol: "ws://", - url: wsUrl, - wsUrl, - }); - - setWorkspaceActiveTab("workspace"); - }; - - return ( -
-
- -
- - -
- -
-
- ); -} +import React from 'react'; +import uuid from 'uuid/v4'; +import historyController from '../../controllers/historyController'; +import WSEndpointEntryForm from './NewRequest/WSEndpointEntryForm'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; +import WSTestEntryForm from './NewRequest/WSTestEntryForm.jsx'; + +function WSContainer({ + setNewTestContent, + resetComposerFields, + setNewRequestFields, + newRequestFields, + newRequestFields: { + url, + restUrl, + webrtc, + wsUrl, + gqlUrl, + grpcUrl, + network, + testContent, + }, + currentTab, + setComposerWarningMessage, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const requestValidationCheck = () => { + const validationMessage = {}; + //Error conditions... + // if url is only http/https/ws/wss:// + // OR if url doesn't contain http/https/ws/wss + if ( + /https?:\/\/$|wss?:\/\/$/.test(url) || + !/(https?:\/\/)|(wss?:\/\/)/.test(url) + ) { + validationMessage.uri = 'Enter a valid URI'; + } + return validationMessage; + }; + + const addNewRequest = () => { + const warnings = requestValidationCheck(); + if (Object.keys(warnings).length > 0) { + setComposerWarningMessage(warnings); + return; + } + + const reqRes = { + id: uuid(), + created_at: new Date(), + protocol: url.match(/wss?:\/\//)[0], + url, + webrtc, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: 'WebSocket', + checkSelected: false, + request: { + method: 'WS', + messages: [], + network, + restUrl, + wsUrl, + gqlUrl, + grpcUrl, + testContent, + }, + response: { + messages: [], + }, + checked: false, + tab: currentTab, + }; + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + + setNewRequestFields({ + ...newRequestFields, + protocol: 'ws://', + url: wsUrl, + wsUrl, + }); + + setWorkspaceActiveTab('workspace'); + }; + + return ( +
+
+ +
+ + +
+ +
+
+ ); +} + +export default WSContainer; diff --git a/src/client/components/composer/WebRTCContainer.jsx b/src/client/components/composer/WebRTCContainer.jsx new file mode 100644 index 000000000..e8453c363 --- /dev/null +++ b/src/client/components/composer/WebRTCContainer.jsx @@ -0,0 +1,140 @@ +import React from 'react'; +import uuid from 'uuid/v4'; +import historyController from '../../controllers/historyController'; +import WebRTCSessionEntryForm from './NewRequest/WebRTCSessionEntryForm.jsx'; +import WebRTCServerEntryForm from './NewRequest/WebRTCServerEntryForm.jsx'; +import NewRequestButton from './NewRequest/NewRequestButton.jsx'; +import TestEntryForm from './NewRequest/TestEntryForm.jsx'; + +function WebRTCContainer({ + resetComposerFields, + setNewRequestFields, + newRequestFields, + newRequestFields: { + gRPC, + url, + method, + protocol, + graphQL, + restUrl, + webrtc, + webrtcUrl, + network, + testContent, + }, + setNewTestContent, + setNewRequestBody, + newRequestBody, + newRequestBody: { rawType, bodyContent, bodyVariables, bodyType }, + setNewRequestHeaders, + webrtcData, + newRequestHeaders, + setNewRequestCookies, + setNewRequestStreams, + newRequestStreams, + currentTab, + setComposerWarningMessage, + warningMessage, + reqResAdd, + setWorkspaceActiveTab, +}) { + const addNewRequest = () => { + const reqRes = { + id: uuid(), + created_at: new Date(), + protocol, + host: '', + path: '', + graphQL, + gRPC, + webrtc, + url, + timeSent: null, + timeReceived: null, + connection: 'uninitialized', + connectionType: null, + checkSelected: false, + webrtcData, + request: { + method, + webrtcData, + url, + messages: [], + body: bodyContent || '', + bodyType, + bodyVariables: bodyVariables || '', + rawType, + network, + restUrl, + webrtcUrl, + }, + response: { + webrtcData, + messages: [], + }, + checked: false, + minimized: false, + tab: currentTab, + }; + + // add request to history + historyController.addHistoryToIndexedDb(reqRes); + reqResAdd(reqRes); + + //reset for next request + resetComposerFields(); + + setNewRequestBody({ + ...newRequestBody, + bodyType: 'stun-ice', + rawType: '', + }); + setNewRequestFields({ + ...newRequestFields, + url, + webrtcUrl, + }); + + setWorkspaceActiveTab('workspace'); + }; + + return ( +
+
+ + + + + +
+
+ +
+
+ ); +} + +export default WebRTCContainer; diff --git a/src/client/components/containers/App.tsx b/src/client/components/containers/App.tsx index fb21239a8..b423ceed5 100644 --- a/src/client/components/containers/App.tsx +++ b/src/client/components/containers/App.tsx @@ -1,12 +1,17 @@ -import React, { useState, useEffect } from "react"; -import { HashRouter } from "react-router-dom"; -import "../../../assets/style/App.scss"; -import { ContentsContainer } from "./ContentsContainer"; -import { SidebarContainer } from "./SidebarContainer"; -import historyController from "../../controllers/historyController"; -import collectionsController from "../../controllers/collectionsController"; -import UpdatePopUpContainer from "./UpdatePopUpContainer"; -import ResponsePaneContainer from "./ResponsePaneContainer"; +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import React, { useState, useEffect } from 'react'; +import { HashRouter } from 'react-router-dom'; +import ContentsContainer from './ContentsContainer'; +import SidebarContainer from './SidebarContainer'; +import historyController from '../../controllers/historyController'; +import collectionsController from '../../controllers/collectionsController'; +import UpdatePopUpContainer from './UpdatePopUpContainer'; +import ResponsePaneContainer from './ResponsePaneContainer'; +import '../../../assets/style/App.scss'; declare global { interface Window { @@ -14,38 +19,34 @@ declare global { } } -let api = window.api; -export const App = () => { +const { api } = window; + +const App = () => { const [message, setMessage] = useState(null); useEffect(() => { - api.send("check-for-update"); - // This file will listen on all of these channels(selectAll, deselectAll, etc) for any communication from the main.js file(aka the main process) - // current disabled as none of us have a touch bar. If activated, follow the api.send method. - - // ipcRenderer.on('selectAll', ReqResCtrl.selectAllReqRes); // if the selectAll touchbar button was clicked (then run this method called selectAllReqRes) that is located in the connectionController...likewise for the rest - // ipcRenderer.on('deselectAll', ReqResCtrl.deselectAllReqRes); - // ipcRenderer.on('openAllSelected', ReqResCtrl.openAllSelectedReqRes); - // ipcRenderer.on('closeAllSelected', ReqResCtrl.closeAllReqRes); - // ipcRenderer.on('minimizeAll', ReqResCtrl.minimizeAllReqRes); - // ipcRenderer.on('expandAll', ReqResCtrl.expandAllReqRes); - // ipcRenderer.on('clearAll', ReqResCtrl.clearAllReqRes); - + api.send('check-for-update'); historyController.getHistory(); collectionsController.getCollections(); - }); - + return ( -
-
+
+
- - - - + + + +
- +
); -} \ No newline at end of file +}; + +export default App; diff --git a/src/client/components/containers/CollectionsContainer.jsx b/src/client/components/containers/CollectionsContainer.jsx index 42c7335ec..5d3122edd 100644 --- a/src/client/components/containers/CollectionsContainer.jsx +++ b/src/client/components/containers/CollectionsContainer.jsx @@ -1,49 +1,50 @@ -import React from "react"; -import { useSelector, useDispatch } from 'react-redux'; -import * as actions from "../../../../src/client/actions/actions.js"; -import Collection from "../display/Collection.jsx"; -import collectionsController from "../../controllers/collectionsController"; - -export default function CollectionsContainer() { - const dispatch = useDispatch(); - - const collections = useSelector(store => store.business.collections); - - const handleClick = () => { - collectionsController.importCollection(collections); - } - - const collectionComponents = collections.map( - (collection, idx) => { - return ( - {dispatch(actions.deleteFromCollection(collection))}} - collectionToReqRes={(reqResArray) => {dispatch(actions.collectionToReqRes(reqResArray))}} - /> - ); - } - ); - - return ( -
- -
- - -
-
- -
- {collectionComponents} -
-
- ); -} +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions.js'; +import Collection from '../display/Collection.jsx'; +import collectionsController from '../../controllers/collectionsController'; + +function CollectionsContainer() { + const dispatch = useDispatch(); + + const collections = useSelector((store) => store.business.collections); + + const handleClick = () => { + collectionsController.importCollection(collections); + }; + + const collectionComponents = collections.map((collection, idx) => { + return ( + { + dispatch(actions.deleteFromCollection(collection)); + }} + collectionToReqRes={(reqResArray) => { + dispatch(actions.collectionToReqRes(reqResArray)); + }} + /> + ); + }); + + return ( +
+
+ + +
+
+ +
{collectionComponents}
+
+ ); +} + +export default CollectionsContainer; diff --git a/src/client/components/containers/ContentsContainer.tsx b/src/client/components/containers/ContentsContainer.tsx index 5fee5bf4c..9f76c25db 100644 --- a/src/client/components/containers/ContentsContainer.tsx +++ b/src/client/components/containers/ContentsContainer.tsx @@ -1,25 +1,34 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState, useEffect } from "react"; +import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import * as actions from '../../actions/actions'; -import BarGraph from "../display/BarGraph" -import WorkspaceContainer from "./WorkspaceContainer.jsx"; -import ScheduleContainer from "./ScheduleContainer.jsx"; -import CollectionsContainer from "./CollectionsContainer"; +import BarGraph from '../display/BarGraph'; +import WorkspaceContainer from './WorkspaceContainer.jsx'; +import ScheduleContainer from './ScheduleContainer.jsx'; +import CollectionsContainer from './CollectionsContainer'; -export const ContentsContainer = () => { - // const [activeTab, setActiveTab] = useState('workspace'); +const ContentsContainer = () => { const dispatch = useDispatch(); - const activeTab = useSelector(store => store.ui.workspaceActiveTab); - const currentResponse = useSelector(store => store.business.currentResponse); - const setActiveTab = (tabName) => dispatch(actions.setWorkspaceActiveTab(tabName)); + const activeTab = useSelector((store) => store.ui.workspaceActiveTab); + const currentResponse = useSelector( + (store) => store.business.currentResponse + ); + const setActiveTab = (tabName) => + dispatch(actions.setWorkspaceActiveTab(tabName)); const [showGraph, setShowGraph] = useState(false); return ( -
+
{/* HEADER */}

Workspace

@@ -27,65 +36,55 @@ export const ContentsContainer = () => { {/* TAB SELECTOR */} - {/* */} {/* WORKSPACE CONTENT */}
+ {activeTab === 'workspace' && } - {activeTab === 'workspace' && - - } - - {activeTab === 'saved-workspace' && - - } - - {activeTab === 'schedule' && - - } + {activeTab === 'saved-workspace' && } + {activeTab === 'schedule' && }
{/* BARGRAPH CONTENT */} - { currentResponse.id && + {currentResponse.id && (
setShowGraph(showGraph === false)} - > - {showGraph && - 'Hide Response History' - } - {!showGraph && - 'View Response History' - } + > + {showGraph && 'Hide Response History'} + {!showGraph && 'View Response History'}
- } - {showGraph && -
+ )} + {showGraph && ( +
-
- } +
+ )}
); -} +}; + +export default ContentsContainer; diff --git a/src/client/components/containers/CookieContainer.jsx b/src/client/components/containers/CookieContainer.jsx index ba0b1331f..14c136273 100644 --- a/src/client/components/containers/CookieContainer.jsx +++ b/src/client/components/containers/CookieContainer.jsx @@ -1,41 +1,45 @@ -import React, { useState } from "react"; - -export default function CookieContainer({ cookie }) { - const [showCookie, setShowCookie] = useState(false); - - const cookies = Object.entries(cookie).map(([key, value], index) => { - if (!key || !value) return; - if (showCookie === true && index > 1) { - return ( - - {key} - {value.toString()} - - ); - } else if (index <= 1) { - return ( - - {key} - {value.toString()} - - ); - } - }); - - return ( - { - setShowCookie(showCookie === false); - }} - > - - - - - - - {cookies} -
KeyValue
- ); -} +/* eslint-disable array-callback-return */ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; + +export default function CookieContainer({ cookie }) { + const [showCookie, setShowCookie] = useState(false); + + const cookies = Object.entries(cookie).map(([key, value], index) => { + if (!key || !value) return; + if (showCookie === true && index > 1) { + return ( + + {key} + {value.toString()} + + ); + } + if (index <= 1) { + return ( + + {key} + {value.toString()} + + ); + } + }); + + return ( + { + setShowCookie(showCookie === false); + }} + > + + + + + + + {cookies} +
KeyValue
+ ); +} diff --git a/src/client/components/containers/CookiesContainer.jsx b/src/client/components/containers/CookiesContainer.jsx index 70822e682..277714a3a 100644 --- a/src/client/components/containers/CookiesContainer.jsx +++ b/src/client/components/containers/CookiesContainer.jsx @@ -1,27 +1,29 @@ -import React from "react"; -import CookieContainer from "./CookieContainer"; -import EmptyState from "../display/EmptyState"; - -export default function CookiesContainer({ currentResponse }) { - if ( - !currentResponse.response || - !currentResponse.response.cookies || - !currentResponse.response.cookies.length - ) { - return ; - } - - const responseCookies = currentResponse.response.cookies.map( - (cookie, index) => { - return ( - - ); - } - ); - - return
{responseCookies}
; -} +import React from 'react'; +import CookieContainer from './CookieContainer'; +import EmptyState from '../display/EmptyState'; + +function CookiesContainer({ currentResponse }) { + if ( + !currentResponse.response || + !currentResponse.response.cookies || + !currentResponse.response.cookies.length + ) { + return ; + } + + const responseCookies = currentResponse.response.cookies.map( + (cookie, index) => { + return ( + + ); + } + ); + + return
{responseCookies}
; +} + +export default CookiesContainer; diff --git a/src/client/components/containers/EventsContainer.jsx b/src/client/components/containers/EventsContainer.jsx index d000506f6..f824f6f1a 100644 --- a/src/client/components/containers/EventsContainer.jsx +++ b/src/client/components/containers/EventsContainer.jsx @@ -1,68 +1,67 @@ -import React from 'react'; -import {UnControlled as CodeMirror} from 'react-codemirror2'; -import EmptyState from '../display/EmptyState'; -import EventPreview from '../display/EventPreview'; -import 'codemirror/theme/neo.css' - - -export default function EventsContainer({currentResponse}) { - - const { request, response } = currentResponse; - if (!response || !response.events || response.events.length < 1) { - return ( - - ); - } - - const { events, headers } = response; - - let responseBody = ''; - - // If it's a stream or graphQL subscription - if ( - (events && events.length > 1) || - (headers?.["content-type"] && headers["content-type"].includes('stream')) || - (currentResponse.graphQL && request.method === 'SUBSCRIPTION') - ) { - - let eventType = 'Stream'; - if (currentResponse.graphQL && request.method === 'SUBSCRIPTION') { - eventType = 'Subscription' - } - - events.forEach((event, idx) => { - const eventStr = JSON.stringify(event, null, 4); - responseBody += `-------------${eventType} Event ${idx + 1}-------------\n${eventStr}\n\n`; - }); - } - // If it's a single response - else { - responseBody = JSON.stringify(events[0], null, 4); - } - return ( -
- {request.method === 'GET' && ( - - )} -
- -
-
- ); - - -} \ No newline at end of file +import React from 'react'; +import { UnControlled as CodeMirror } from 'react-codemirror2'; +import EmptyState from '../display/EmptyState'; +import EventPreview from '../display/EventPreview'; +import 'codemirror/theme/neo.css'; + +function EventsContainer({ currentResponse }) { + const { request, response } = currentResponse; + if (!response || !response.events || response.events.length < 1) { + return ; + } + const { events, headers } = response; + + let responseBody = ''; + + // If it's a stream or graphQL subscription + if ( + (events && events.length > 1) || + (headers?.['content-type'] && headers['content-type'].includes('stream')) || + (currentResponse.graphQL && request.method === 'SUBSCRIPTION') + ) { + let eventType = 'Stream'; + if (currentResponse.graphQL && request.method === 'SUBSCRIPTION') { + eventType = 'Subscription'; + } + + events.forEach((event, idx) => { + const eventStr = JSON.stringify(event, null, 4); + responseBody += `-------------${eventType} Event ${ + idx + 1 + }-------------\n${eventStr}\n\n`; + }); + } + // If it's a single response + else { + responseBody = JSON.stringify(events[0], null, 4); + } + return ( +
+ {request.method === 'GET' && ( + + )} +
+ +
+
+ ); +} + +export default EventsContainer; diff --git a/src/client/components/containers/HeadersContainer.jsx b/src/client/components/containers/HeadersContainer.jsx index 1e8b2d89f..6cd3eb37f 100644 --- a/src/client/components/containers/HeadersContainer.jsx +++ b/src/client/components/containers/HeadersContainer.jsx @@ -1,41 +1,43 @@ -import React from "react"; -import EmptyState from "../display/EmptyState"; - -export default function HeadersContainer({ currentResponse }) { - if ( - !currentResponse.response || - !currentResponse.response.headers || - Object.entries(currentResponse.response.headers).length === 0 - ) { - return ; - } - - const responseHeaders = Object.entries(currentResponse.response.headers).map( - ([key, value], index) => { - return ( - - {key} - {value} - - ); - } - ); - - return ( -
-
-
- - - - - - - - {responseHeaders} -
KeyValue
-
-
-
- ); -} +import React from 'react'; +import EmptyState from '../display/EmptyState'; + +function HeadersContainer({ currentResponse }) { + if ( + !currentResponse.response || + !currentResponse.response.headers || + Object.entries(currentResponse.response.headers).length === 0 + ) { + return ; + } + + const responseHeaders = Object.entries(currentResponse.response.headers).map( + ([key, value], index) => { + return ( + + {key} + {value} + + ); + } + ); + + return ( +
+
+
+ + + + + + + + {responseHeaders} +
KeyValue
+
+
+
+ ); +} + +export default HeadersContainer; diff --git a/src/client/components/containers/HistoryContainer.jsx b/src/client/components/containers/HistoryContainer.jsx index 56ae14b5a..6425e25d6 100644 --- a/src/client/components/containers/HistoryContainer.jsx +++ b/src/client/components/containers/HistoryContainer.jsx @@ -1,65 +1,80 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import * as actions from '../../actions/actions'; -import HistoryDate from '../display/HistoryDate.jsx'; -import ClearHistoryBtn from '../display/ClearHistoryBtn.jsx'; - -const mapStateToProps = store => ({ - history: store.business.history, - newRequestFields: store.business.newRequestFields, - newRequestStreams: store.business.newRequestStreams -}); - -const mapDispatchToProps = dispatch => ({ - clearHistory: () => { dispatch(actions.clearHistory()) }, - deleteFromHistory: (reqRes) => { dispatch(actions.deleteFromHistory(reqRes)) }, - setNewRequestHeaders: (requestHeadersObj) => { dispatch(actions.setNewRequestHeaders(requestHeadersObj)) }, - setNewRequestFields: (requestFields) => { dispatch(actions.setNewRequestFields(requestFields)) }, - setNewRequestBody: (requestBodyObj) => { dispatch(actions.setNewRequestBody(requestBodyObj)) }, - setNewRequestCookies: (requestCookiesObj) => { dispatch(actions.setNewRequestCookies(requestCookiesObj)) }, - setNewRequestStreams: (requestStreamsObj) => { dispatch(actions.setNewRequestStreams(requestStreamsObj))} -}); - -const HistoryContainer = (props) => { - const { - history, - clearHistory, - deleteFromHistory, - setNewRequestFields, - setNewRequestHeaders, - setNewRequestCookies, - setNewRequestBody, - setNewRequestStreams, - } = props; - - // history is already sorted by created_at from getHistory - // 1) map through history state and create date component. 2) pass props to new component - const historyDates = history.map((date, i) => { - return - }) - - return ( -
-
- -
-
{historyDates}
-
- ) -} - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(HistoryContainer); \ No newline at end of file +import React from 'react'; +import { connect } from 'react-redux'; +import * as actions from '../../actions/actions'; +import HistoryDate from '../display/HistoryDate.jsx'; +import ClearHistoryBtn from '../display/ClearHistoryBtn.jsx'; + +const mapStateToProps = (store) => ({ + history: store.business.history, + newRequestFields: store.business.newRequestFields, + newRequestStreams: store.business.newRequestStreams, +}); + +const mapDispatchToProps = (dispatch) => ({ + clearHistory: () => { + dispatch(actions.clearHistory()); + }, + deleteFromHistory: (reqRes) => { + dispatch(actions.deleteFromHistory(reqRes)); + }, + setNewRequestHeaders: (requestHeadersObj) => { + dispatch(actions.setNewRequestHeaders(requestHeadersObj)); + }, + setNewRequestFields: (requestFields) => { + dispatch(actions.setNewRequestFields(requestFields)); + }, + setNewRequestBody: (requestBodyObj) => { + dispatch(actions.setNewRequestBody(requestBodyObj)); + }, + setNewRequestCookies: (requestCookiesObj) => { + dispatch(actions.setNewRequestCookies(requestCookiesObj)); + }, + setNewRequestStreams: (requestStreamsObj) => { + dispatch(actions.setNewRequestStreams(requestStreamsObj)); + }, +}); + +const HistoryContainer = (props) => { + const { + history, + clearHistory, + deleteFromHistory, + setNewRequestFields, + setNewRequestHeaders, + setNewRequestCookies, + setNewRequestBody, + setNewRequestStreams, + } = props; + + // history is already sorted by created_at from getHistory + const historyDates = history.map((date, i) => { + return ( + + ); + }); + + return ( +
+
+ +
+
{historyDates}
+
+ ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(HistoryContainer); diff --git a/src/client/components/containers/ReqResContainer.jsx b/src/client/components/containers/ReqResContainer.jsx index 68b5c02a8..cf0ba8a0d 100644 --- a/src/client/components/containers/ReqResContainer.jsx +++ b/src/client/components/containers/ReqResContainer.jsx @@ -1,62 +1,61 @@ -import React, { useEffect } from "react"; -import { connect, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions"; -import SingleReqResContainer from "./SingleReqResContainer.jsx"; -import ReqResCtrl from "../../controllers/reqResController"; - -const mapStateToProps = (store) => ({ - reqResArray: store.business.reqResArray, - currentTab: store.business.currentTab, -}); - -const mapDispatchToProps = (dispatch) => ({ - reqResDelete: (reqRes) => { - dispatch(actions.reqResDelete(reqRes)); - }, - reqResUpdate: (reqRes) => { - dispatch(actions.reqResUpdate(reqRes)); - }, -}); - -const ReqResContainer = (props) => { - const { reqResArray, reqResDelete, reqResUpdate, displaySchedule } = props; - const dispatch = useDispatch(); - - const reqResMapped = reqResArray.map((reqRes, index) => { - return ( - - ); - }); - - const runCollectionTest = () => { - ReqResCtrl.runCollectionTest(reqResArray); - }; - - return ( -
- {reqResArray.length > 0 && displaySchedule && ( -
- -
- )} - -
{reqResMapped.reverse()}
-
- ); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(ReqResContainer); +import React from 'react'; +import { connect } from 'react-redux'; +import * as actions from '../../actions/actions'; +import SingleReqResContainer from './SingleReqResContainer.jsx'; +import ReqResCtrl from '../../controllers/reqResController'; + +const mapStateToProps = (store) => ({ + reqResArray: store.business.reqResArray, + currentTab: store.business.currentTab, +}); + +const mapDispatchToProps = (dispatch) => ({ + reqResDelete: (reqRes) => { + dispatch(actions.reqResDelete(reqRes)); + }, + reqResUpdate: (reqRes) => { + dispatch(actions.reqResUpdate(reqRes)); + }, +}); + +const ReqResContainer = (props) => { + const { reqResArray, reqResDelete, reqResUpdate, displaySchedule } = props; + + const reqResMapped = reqResArray.map((reqRes, index) => { + return ( + + ); + }); + + const runCollectionTest = () => { + ReqResCtrl.runCollectionTest(reqResArray); + }; + + return ( +
+ {reqResArray.length > 0 && displaySchedule && ( +
+ +
+ )} + +
{reqResMapped.reverse()}
+
+ ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(ReqResContainer); diff --git a/src/client/components/containers/ResponsePaneContainer.jsx b/src/client/components/containers/ResponsePaneContainer.jsx index ae2a22e5f..0fcf8ad60 100644 --- a/src/client/components/containers/ResponsePaneContainer.jsx +++ b/src/client/components/containers/ResponsePaneContainer.jsx @@ -1,184 +1,183 @@ -import React from "react"; -import { useSelector, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions"; -import EventsContainer from "./EventsContainer"; -import HeadersContainer from "./HeadersContainer"; -import TestsContainer from "./TestsContainer"; -import CookiesContainer from "./CookiesContainer"; -import StatusButtons from "../display/StatusButtons"; -import ResponseTime from "../display/ResponseTime"; -import WebSocketWindow from "../display/WebSocketWindow"; -import ReqResCtrl from "../../controllers/reqResController"; - -/// MAKE SURE YOU UPDATE THE INTERNAL COMMENTS BEFORE SENDING IT TO GITHUB - -export const ResponsePaneContainer = () => { - const dispatch = useDispatch(); - const activeTab = useSelector((store) => store.ui.responsePaneActiveTab); - - const setActiveTab = (tabName) => - dispatch(actions.setResponsePaneActiveTab(tabName)); - - const currentResponse = useSelector( - (store) => store.business.currentResponse - ); - const { connection } = currentResponse; - - // UNCOMMENT FOR DEBUGGING - console.log("currentResponse on ResponsePaneContainer --> ", currentResponse); - - return ( -
- {/* HEADER */} -
- - {currentResponse.responseSize && ( -
- {`${currentResponse.responseSize}kb`} -
- )} -

Responses

- -
-
- {/* TAB SELECTOR */} -
- -
- {/* RESPONSES CONTENT */} -
- {activeTab === "events" && ( - - )} - {activeTab === "headers" && ( - - )} - {activeTab === "cookies" && ( - - )} - {activeTab === "tests" && ( - - )} - {/* currentResponse.request?.network === "ws" */} - {activeTab === "wsWindow" && - currentResponse.request && - currentResponse.response && - currentResponse.request && ( - - )} -
- {/* RENDER RE-SEND REQUEST BUTTON ONLY FOR NOT WEB SOCKETS / SUBSCRIPTIONS */} - {currentResponse.id && - currentResponse.request?.method !== "WS" && - currentResponse.request?.method !== "SUBSCRIPTION" && - (connection === "closed" || connection === "error") && ( -
- -
- )} -
- - {/* CLOSE RESPONSE BUTTON */} - - {(currentResponse.request?.method === "WS" || - currentResponse.request?.method === "SUBSCRIPTION" || - currentResponse.request?.isSSE || - currentResponse.isHTTP2) && - connection === "open" && ( -
- -
- )} - {/* RENDER RE-OPEN CONNECTION BUTTON ONLY FOR OPEN WEB SOCKETS / SUBSCRIPTIONS */} - {(currentResponse.request?.method === "WS" || - currentResponse.request?.method === "SUBSCRIPTION") && - (connection === "closed" || connection === "error") && ( -
- -
- )} -
- ); -}; - -export default ResponsePaneContainer; +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions'; +import EventsContainer from './EventsContainer'; +import HeadersContainer from './HeadersContainer'; +import TestsContainer from './TestsContainer'; +import CookiesContainer from './CookiesContainer'; +import StatusButtons from '../display/StatusButtons'; +import ResponseTime from '../display/ResponseTime'; +import WebSocketWindow from '../display/WebSocketWindow'; +import ReqResCtrl from '../../controllers/reqResController'; + +const ResponsePaneContainer = () => { + const dispatch = useDispatch(); + const activeTab = useSelector((store) => store.ui.responsePaneActiveTab); + + const setActiveTab = (tabName) => + dispatch(actions.setResponsePaneActiveTab(tabName)); + + const currentResponse = useSelector( + (store) => store.business.currentResponse + ); + const { connection } = currentResponse; + + // UNCOMMENT FOR DEBUGGING + console.log('currentResponse on ResponsePaneContainer --> ', currentResponse); + + return ( +
+ {/* HEADER */} +
+ + {currentResponse.responseSize && ( +
+ {`${currentResponse.responseSize}kb`} +
+ )} +

Responses

+ +
+
+ {/* TAB SELECTOR */} +
+ +
+ {/* RESPONSES CONTENT */} +
+ {activeTab === 'events' && ( + + )} + {activeTab === 'headers' && ( + + )} + {activeTab === 'cookies' && ( + + )} + {activeTab === 'tests' && ( + + )} + {/* currentResponse.request?.network === "ws" */} + {activeTab === 'wsWindow' && + currentResponse.request && + currentResponse.response && + currentResponse.request && ( + + )} +
+ {/* RENDER RE-SEND REQUEST BUTTON ONLY FOR NOT WEB SOCKETS / SUBSCRIPTIONS */} + {currentResponse.id && + currentResponse.request?.method !== 'WS' && + currentResponse.request?.method !== 'SUBSCRIPTION' && + (connection === 'closed' || connection === 'error') && ( +
+ +
+ )} +
+ {/* CLOSE RESPONSE BUTTON */} + {(currentResponse.request?.method === 'WS' || + currentResponse.request?.method === 'SUBSCRIPTION' || + currentResponse.request?.isSSE || + currentResponse.isHTTP2) && + connection === 'open' && ( +
+ +
+ )} + {/* RENDER RE-OPEN CONNECTION BUTTON ONLY FOR OPEN WEB SOCKETS / SUBSCRIPTIONS */} + {(currentResponse.request?.method === 'WS' || + currentResponse.request?.method === 'SUBSCRIPTION') && + (connection === 'closed' || connection === 'error') && ( +
+ +
+ )} +
+ ); +}; + +export default ResponsePaneContainer; diff --git a/src/client/components/containers/SaveWorkspaceModal.jsx b/src/client/components/containers/SaveWorkspaceModal.jsx index e5c5c8092..c7505b83f 100644 --- a/src/client/components/containers/SaveWorkspaceModal.jsx +++ b/src/client/components/containers/SaveWorkspaceModal.jsx @@ -1,143 +1,158 @@ -import React, { useState } from "react"; -import { useSelector, useDispatch } from 'react-redux'; -import uuid from "uuid/v4"; -import collectionsController from "../../controllers/collectionsController.js"; -import SaveModalSavedWorkspaces from "../display/SaveModalSavedWorkspaces.jsx"; -import * as actions from "../../actions/actions.js"; - -export default function SaveWorkspaceModal({ showModal, setShowModal, match }) { - const dispatch = useDispatch(); - // LOCAL STATE HOOKS - const [input, setInput] = useState(''); - const [collectionNameErrorStyles, setCollectionNameErrorStyles] = useState(false); - // PULL elements FROM store - const reqResArray = useSelector(store => store.business.reqResArray); - const collections = useSelector(store => store.business.collections); - - - - const saveCollection = (inputName) => { - const clonedArray = JSON.parse(JSON.stringify(reqResArray)); - clonedArray.forEach((reqRes) => { - //reinitialize and minimize all things - reqRes.checked = false; - reqRes.minimized = true; - reqRes.timeSent = null; - reqRes.timeReceived = null; - reqRes.connection = "uninitialized"; - if (reqRes.response.hasOwnProperty("headers")) - reqRes.response = { headers: null, events: null }; - else reqRes.response = { messages: [] }; - }); - const collectionObj = { - name: inputName, - id: uuid(), - created_at: new Date(), - reqResArray: clonedArray, - }; - collectionsController.addCollectionToIndexedDb(collectionObj); //add to IndexedDB - dispatch(actions.collectionAdd(collectionObj)); - setShowModal(false); - setCollectionNameErrorStyles(false); - } - - const updateCollection = (inputName, inputID) => { - const clonedArray = reqResArray.slice(); - clonedArray.forEach((reqRes) => { - //reinitialize and minimize all things - reqRes.checked = false; - reqRes.minimized = true; - reqRes.timeSent = null; - reqRes.timeReceived = null; - reqRes.connection = "uninitialized"; - if (reqRes.response.hasOwnProperty("headers")) - reqRes.response = { headers: null, events: null }; - else reqRes.response = { messages: [] }; - }); - const collectionObj = { - name: inputName, - id: inputID, - created_at: new Date(), - reqResArray: clonedArray, - }; - collectionsController.updateCollectionInIndexedDb(collectionObj); //add to IndexedDB - dispatch(actions.collectionUpdate(collectionObj)); - setShowModal(false); - setCollectionNameErrorStyles(false); - } - - const saveName = () => { - if (input.trim()) { - collectionsController - .collectionNameExists({ name: input }) - .catch((err) => - console.error("error in checking collection name: ", err) - ) - .then((found) => { - if (found) { - //if the name already exists, change style state - setCollectionNameErrorStyles(true); - } else saveCollection(input); - }); - } - } - - const workspaceComponents = collections.map( - (workspace, idx) => { - return ( - - ); - } - ); - - return ( -
- {showModal && -
-
{ setShowModal(false) }} - /> -
-
- {/* CUSTOM MODAL */} - {/* SELECT EXISTING SAVED WORKSPACE TO WRITE OVER */} -

Select saved workspace to write over

- {workspaceComponents} -
- {/* INPUT YOUR OWN NAME */} -

Name your saved workspace

-
- setInput(e.target.value) } - autoFocus - className="input" - /> -
- { collectionNameErrorStyles && -

- Collection name already exists! -

- } -
- - -
-
-
-
- } -
- ) -} +/* eslint-disable no-prototype-builtins */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import uuid from 'uuid/v4'; +import collectionsController from '../../controllers/collectionsController.js'; +import SaveModalSavedWorkspaces from '../display/SaveModalSavedWorkspaces.jsx'; +import * as actions from '../../actions/actions.js'; + +function SaveWorkspaceModal({ showModal, setShowModal, match }) { + const dispatch = useDispatch(); + // LOCAL STATE HOOKS + const [input, setInput] = useState(''); + const [collectionNameErrorStyles, setCollectionNameErrorStyles] = + useState(false); + // PULL elements FROM store + const reqResArray = useSelector((store) => store.business.reqResArray); + const collections = useSelector((store) => store.business.collections); + + const saveCollection = (inputName) => { + const clonedArray = JSON.parse(JSON.stringify(reqResArray)); + clonedArray.forEach((reqRes) => { + //reinitialize and minimize all things + reqRes.checked = false; + reqRes.minimized = true; + reqRes.timeSent = null; + reqRes.timeReceived = null; + reqRes.connection = 'uninitialized'; + if (reqRes.response.hasOwnProperty('headers')) + reqRes.response = { headers: null, events: null }; + else reqRes.response = { messages: [] }; + }); + const collectionObj = { + name: inputName, + id: uuid(), + created_at: new Date(), + reqResArray: clonedArray, + }; + collectionsController.addCollectionToIndexedDb(collectionObj); //add to IndexedDB + dispatch(actions.collectionAdd(collectionObj)); + setShowModal(false); + setCollectionNameErrorStyles(false); + }; + + const updateCollection = (inputName, inputID) => { + const clonedArray = reqResArray.slice(); + clonedArray.forEach((reqRes) => { + //reinitialize and minimize all things + reqRes.checked = false; + reqRes.minimized = true; + reqRes.timeSent = null; + reqRes.timeReceived = null; + reqRes.connection = 'uninitialized'; + if (reqRes.response.hasOwnProperty('headers')) + reqRes.response = { headers: null, events: null }; + else reqRes.response = { messages: [] }; + }); + const collectionObj = { + name: inputName, + id: inputID, + created_at: new Date(), + reqResArray: clonedArray, + }; + collectionsController.updateCollectionInIndexedDb(collectionObj); //add to IndexedDB + dispatch(actions.collectionUpdate(collectionObj)); + setShowModal(false); + setCollectionNameErrorStyles(false); + }; + + const saveName = () => { + if (input.trim()) { + collectionsController + .collectionNameExists({ name: input }) + .catch((err) => + console.error('error in checking collection name: ', err) + ) + .then((found) => { + if (found) { + //if the name already exists, change style state + setCollectionNameErrorStyles(true); + } else saveCollection(input); + }); + } + }; + + const workspaceComponents = collections.map((workspace, idx) => { + return ( + + ); + }); + + return ( +
+ {showModal && ( +
+
{ + setShowModal(false); + }} + /> +
+
+ {/* CUSTOM MODAL */} + {/* SELECT EXISTING SAVED WORKSPACE TO WRITE OVER */} +

Select saved workspace to write over

+ {workspaceComponents} +
+ {/* INPUT YOUR OWN NAME */} +

Name your saved workspace

+
+ setInput(e.target.value)} + className="input" + /> +
+ {collectionNameErrorStyles && ( +

+ Collection name already exists! +

+ )} +
+ + +
+
+
+
+ )} +
+ ); +} + +export default SaveWorkspaceModal; diff --git a/src/client/components/containers/ScheduleContainer.jsx b/src/client/components/containers/ScheduleContainer.jsx index 5032f8176..453cb55be 100644 --- a/src/client/components/containers/ScheduleContainer.jsx +++ b/src/client/components/containers/ScheduleContainer.jsx @@ -1,55 +1,58 @@ -import React, { useState } from "react"; -import ReqResCtrl from "../../controllers/reqResController.js"; -import ScheduleReqResContainer from "./ScheduleReqResContainer.jsx"; -import StoppedContainer from "./StoppedContainer.jsx"; -import ReqResContainer from "./ReqResContainer.jsx"; - -export default function ScheduleContainer() { - const [scheduleInterval, setScheduleInterval] = useState(1); - const [runScheduledTests, setScheduledTests] = useState(false); - - return ( -
-
-
- Frequency (Seconds): - {setScheduleInterval(e.target.value)}} - /> -
-
- - -
-
-
- -
- {runScheduledTests && - - } - {!runScheduledTests && } -
- ); -} +import React, { useState } from 'react'; +import ScheduleReqResContainer from './ScheduleReqResContainer.jsx'; +import StoppedContainer from './StoppedContainer.jsx'; +import ReqResContainer from './ReqResContainer.jsx'; + +function ScheduleContainer() { + const [scheduleInterval, setScheduleInterval] = useState(1); + const [runScheduledTests, setScheduledTests] = useState(false); + + return ( +
+
+
+ + Frequency (Seconds): + + { + setScheduleInterval(e.target.value); + }} + /> +
+
+ + +
+
+
+ +
+ {runScheduledTests && ( + + )} + {!runScheduledTests && } +
+ ); +} + +export default ScheduleContainer; diff --git a/src/client/components/containers/ScheduleReqResContainer.jsx b/src/client/components/containers/ScheduleReqResContainer.jsx index 783cafc83..0f2f3718e 100644 --- a/src/client/components/containers/ScheduleReqResContainer.jsx +++ b/src/client/components/containers/ScheduleReqResContainer.jsx @@ -1,61 +1,67 @@ -import React, { useEffect, useState } from "react"; -import { connect, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions"; -import SingleScheduleReqResContainer from "./SingleScheduleReqResContainer.jsx"; -import SingleReqResContainer from "./SingleReqResContainer.jsx"; -import ReqResCtrl from "../../controllers/reqResController"; - -const mapStateToProps = (store) => ({ - reqResArray: store.business.reqResArray, - scheduledReqResArray: store.business.scheduledReqResArray, - currentTab: store.business.currentTab, -}); - -const mapDispatchToProps = (dispatch) => ({ - reqResDelete: (reqRes) => { - dispatch(actions.reqResDelete(reqRes)); - }, - reqResUpdate: (reqRes) => { - dispatch(actions.reqResUpdate(reqRes)); - }, -}); - -const ScheduleReqResContainer = (props) => { - const { reqResArray, reqResDelete, reqResUpdate, scheduleInterval, scheduledReqResArray} = props; - const dispatch = useDispatch(); - - useEffect(() => { - const interval = setInterval(() => { - for (let i = 0; i < reqResArray.length; i++) { - ReqResCtrl.openScheduledReqRes(reqResArray[i].id); - } - }, scheduleInterval*1000); - return () => clearInterval(interval); - }, []); - - let scheduledReqResMapped = scheduledReqResArray.map((reqRes, index) => { - return ( - - ); - }); - - - return ( -
-
-
Scheduled Requests
- {scheduledReqResMapped.reverse()} -
-
- ); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(ScheduleReqResContainer); +import React, { useEffect } from 'react'; +import { connect } from 'react-redux'; +import * as actions from '../../actions/actions'; +import SingleScheduleReqResContainer from './SingleScheduleReqResContainer.jsx'; +import ReqResCtrl from '../../controllers/reqResController'; + +const mapStateToProps = (store) => ({ + reqResArray: store.business.reqResArray, + scheduledReqResArray: store.business.scheduledReqResArray, + currentTab: store.business.currentTab, +}); + +const mapDispatchToProps = (dispatch) => ({ + reqResDelete: (reqRes) => { + dispatch(actions.reqResDelete(reqRes)); + }, + reqResUpdate: (reqRes) => { + dispatch(actions.reqResUpdate(reqRes)); + }, +}); + +const ScheduleReqResContainer = (props) => { + const { + reqResArray, + reqResDelete, + reqResUpdate, + scheduleInterval, + scheduledReqResArray, + } = props; + + useEffect(() => { + const interval = setInterval(() => { + for (let i = 0; i < reqResArray.length; i++) { + ReqResCtrl.openScheduledReqRes(reqResArray[i].id); + } + }, scheduleInterval * 1000); + return () => clearInterval(interval); + }, [reqResArray, scheduleInterval]); + + const scheduledReqResMapped = scheduledReqResArray.map((reqRes, index) => { + return ( + + ); + }); + + return ( +
+
+
Scheduled Requests
+ {scheduledReqResMapped.reverse()} +
+
+ ); +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ScheduleReqResContainer); diff --git a/src/client/components/containers/SidebarContainer.tsx b/src/client/components/containers/SidebarContainer.tsx index bda2d2a45..e003db2b0 100644 --- a/src/client/components/containers/SidebarContainer.tsx +++ b/src/client/components/containers/SidebarContainer.tsx @@ -1,19 +1,28 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/anchor-is-valid */ -import React, { useState } from "react"; +import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import * as actions from '../../actions/actions'; -import ComposerContainer from "../composer/ComposerContainer.jsx"; -import HistoryContainer from "./HistoryContainer.jsx"; +import ComposerContainer from '../composer/ComposerContainer.jsx'; +import HistoryContainer from './HistoryContainer.jsx'; -export const SidebarContainer = () => { +const SidebarContainer = () => { const dispatch = useDispatch(); - const activeTab = useSelector(store => store.ui.sidebarActiveTab); - const setActiveTab = (tabName: string) => dispatch(actions.setSidebarActiveTab(tabName)); + const activeTab = useSelector((store) => store.ui.sidebarActiveTab); + + const setActiveTab = (tabName: string) => + dispatch(actions.setSidebarActiveTab(tabName)); return ( -
+
{/* HEADER */}

Composer

@@ -21,28 +30,23 @@ export const SidebarContainer = () => { {/* TAB SELECTOR */} {/* SIDEBAR CONTENT */} - { - activeTab === "composer" && - - } - {activeTab === "history" && - - } - + {activeTab === 'composer' && } + {activeTab === 'history' && }
); -} +}; + +export default SidebarContainer; diff --git a/src/client/components/containers/SingleReqResContainer.jsx b/src/client/components/containers/SingleReqResContainer.jsx index 796c4cf5a..d96121897 100644 --- a/src/client/components/containers/SingleReqResContainer.jsx +++ b/src/client/components/containers/SingleReqResContainer.jsx @@ -1,312 +1,338 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions.js"; - -import connectionController from "../../controllers/reqResController"; -import RestRequestContent from "../display/RestRequestContent.jsx"; -import GraphQLRequestContent from "../display/GraphQLRequestContent.jsx"; -import GRPCRequestContent from "../display/GRPCRequestContent.jsx"; - -const SingleReqResContainer = (props) => { - const [showDetails, setShowDetails] = useState(false); - const dispatch = useDispatch(); - - const currentResponse = useSelector( - (store) => store.business.currentResponse - ); - - const newRequestFields = useSelector( - (store) => store.business.newRequestFields - ); - const newRequestStreams = useSelector( - (store) => store.business.newRequestStreams - ); - //content is reqRes drilled down from ReqResContainer. reqRes was created in WSContainer/RestContainer... - const { - content, - content: { - id, - graphQL, - closeCode, - protocol, - request, - response, - connection, - connectionType, - isHTTP2, - url, - timeReceived, - timeSent, - rpc, - service, - }, - reqResUpdate, - reqResDelete, - index, - } = props; - const network = content.request.network; - const method = content.request.method; - - const copyToComposer = () => { - let requestFieldObj = {}; - if (network === "rest") { - requestFieldObj = { - ...newRequestFields, - method: content.request.method || "GET", - protocol: content.protocol || "http://", - url: content.url, - restUrl: content.request.restUrl, - graphQL: content.graphQL || false, - gRPC: content.gRPC || false, - network, - testContent: content.request.testContent, - }; - } - if (network === "ws") { - requestFieldObj = { - ...newRequestFields, - method: content.request.method || "GET", - protocol: content.protocol || "http://", - url: content.url, - wsUrl: content.request.wsUrl, - graphQL: content.graphQL || false, - gRPC: content.gRPC || false, - network, - }; - } - if (network === "graphQL") { - requestFieldObj = { - ...newRequestFields, - method: content.request.method || "GET", - protocol: content.protocol || "http://", - url: content.url, - gqlUrl: content.request.gqlUrl, - graphQL: content.graphQL || false, - gRPC: content.gRPC || false, - network, - testContent: content.request.testContent, - }; - } - if (network === "grpc") { - requestFieldObj = { - ...newRequestFields, - method: content.request.method || "GET", - protocol: content.protocol || "http://", - url: content.url, - grpcUrl: content.request.grpcUrl, - graphQL: content.graphQL || false, - gRPC: content.gRPC || false, - network, - testContent: content.request.testContent, - }; - } - let headerDeeperCopy; - if (content.request.headers) { - headerDeeperCopy = JSON.parse(JSON.stringify(content.request.headers)); - headerDeeperCopy.push({ - id: content.request.headers.length + 1, - active: false, - key: "", - value: "", - }); - } - let cookieDeeperCopy; - if (content.request.cookies && !/ws/.test(protocol)) { - cookieDeeperCopy = JSON.parse(JSON.stringify(content.request.cookies)); - cookieDeeperCopy.push({ - id: content.request.cookies.length + 1, - active: false, - key: "", - value: "", - }); - } - const requestHeadersObj = { - headersArr: headerDeeperCopy || [], - count: headerDeeperCopy ? headerDeeperCopy.length : 1, - }; - const requestCookiesObj = { - cookiesArr: cookieDeeperCopy || [], - count: cookieDeeperCopy ? cookieDeeperCopy.length : 1, - }; - const requestBodyObj = { - bodyType: content.request.bodyType || "raw", - bodyContent: content.request.body || "", - bodyVariables: content.request.bodyVariables || "", - rawType: content.request.rawType || "Text (text/plain)", - JSONFormatted: true, - bodyIsNew: false, - }; - dispatch(actions.setNewRequestFields(requestFieldObj)); - dispatch(actions.setNewRequestHeaders(requestHeadersObj)); - dispatch(actions.setNewRequestCookies(requestCookiesObj)); - dispatch(actions.setNewRequestBody(requestBodyObj)); - dispatch(actions.setNewRequestSSE(content.request.isSSE)); - - if (content && content.gRPC) { - const streamsDeepCopy = JSON.parse(JSON.stringify(content.streamsArr)); - const contentsDeepCopy = JSON.parse( - JSON.stringify(content.streamContent) - ); - // construct the streams obj from passed in history content & set state in store - - const requestStreamsObj = { - streamsArr: streamsDeepCopy, - count: content.queryArr.length, - streamContent: contentsDeepCopy, - selectedPackage: content.packageName, - selectedRequest: content.rpc, - selectedService: content.service, - selectedStreamingType: content.request.method, - initialQuery: content.initialQuery, - queryArr: content.queryArr, - protoPath: content.protoPath, - services: content.servicesObj, - protoContent: content.protoContent, - }; - dispatch(actions.setNewRequestStreams(requestStreamsObj)); - } - - dispatch(actions.setSidebarActiveTab("composer")); - }; - - const removeReqRes = () => { - connectionController.closeReqRes(content); - reqResDelete(content); - }; - - const getBorderClass = () => { - let classes = "highlighted-response "; - if (currentResponse.gRPC) classes += "is-grpc-border"; - else if (currentResponse.graphQL) classes += "is-graphQL-border"; - else if (currentResponse.request.method === "WS") classes += "is-ws-border"; - else classes += "is-rest-border"; - return classes; - }; - - const highlightClasses = - currentResponse.id === content.id ? getBorderClass(currentResponse) : ""; - - return ( -
- {/* TITLE BAR */} -
-
- {request.method} -
-
-
{url}
- {/* RENDER STATUS */} -
- {connection === "uninitialized" && ( -
- )} - {connection === "error" &&
} - {connection === "open" &&
} - {connection === "closed" && - method !== "WS" && - method !== "SUBSCRIPTION" && ( -
- )} - {connection === "closed" && - (method === "WS" || method === "SUBSCRIPTION") && ( -
- )} -
-
-
- {/* VIEW REQUEST DETAILS / MINIMIZE */} - {network !== "ws" && ( -
{ - setShowDetails(showDetails === false); - }} - > - {showDetails === true && "Hide Request Details"} - {showDetails === false && "View Request Details"} - {showDetails === true && ( -
- Copy to Composer -
- )} -
- )} - {/* REQUEST ELEMENTS */} - {showDetails === true && ( -
- {network === "rest" && ( - - )} - {network === "grpc" && ( - - )} - {network === "graphQL" && ( - - )} -
- )} - {/* REMOVE / SEND BUTTONS */} -
- - {/* SEND BUTTON */} - {connection === "uninitialized" && ( - - )} - {/* VIEW RESPONSE BUTTON */} - {connection !== "uninitialized" && ( - - )} -
-
- ); -}; -export default SingleReqResContainer; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useEffect, useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions.js'; +import connectionController from '../../controllers/reqResController'; +import RestRequestContent from '../display/RestRequestContent.jsx'; +import GraphQLRequestContent from '../display/GraphQLRequestContent.jsx'; +import WebRTCRequestContent from '../display/WebRTCRequestContent.jsx'; +import GRPCRequestContent from '../display/GRPCRequestContent.jsx'; +import OpenAPIRequestContent from '../display/OpenAPIRequestContent.jsx'; + +const SingleReqResContainer = (props) => { + const [showDetails, setShowDetails] = useState(false); + const dispatch = useDispatch(); + + const currentResponse = useSelector( + (store) => store.business.currentResponse + ); + + const newRequestFields = useSelector( + (store) => store.business.newRequestFields + ); + + const { + content, + content: { protocol, request, connection, connectionType, isHTTP2, url }, + + reqResDelete, + index, + } = props; + const network = content.request.network; + const method = content.request.method; + + useEffect(() => { + if (content.request.network === 'webrtc') { + setShowDetails(true); + } + }, [content.request.network]); + + const copyToComposer = () => { + let requestFieldObj = {}; + + if (network === 'rest') { + requestFieldObj = { + ...newRequestFields, + method: content.request.method || 'GET', + protocol: content.protocol || 'http://', + url: content.url, + restUrl: content.request.restUrl, + graphQL: content.graphQL || false, + gRPC: content.gRPC || false, + webrtc: content.webrtc || false, + network, + testContent: content.request.testContent, + }; + } + + if (network === 'ws') { + requestFieldObj = { + ...newRequestFields, + method: content.request.method || 'GET', + protocol: content.protocol || 'http://', + url: content.url, + wsUrl: content.request.wsUrl, + graphQL: content.graphQL || false, + gRPC: content.gRPC || false, + network, + }; + } + + if (network === 'webrtc') { + requestFieldObj = { + ...newRequestFields, + method: content.request.method || 'GET', + protocol: content.protocol || 'http://', + url: content.url, + wsUrl: content.request.wsUrl, + graphQL: content.graphQL || false, + gRPC: content.gRPC || false, + network, + webrtcData: content.webrtcData, + }; + } + + if (network === 'graphQL') { + requestFieldObj = { + ...newRequestFields, + method: content.request.method || 'GET', + protocol: content.protocol || 'http://', + url: content.url, + gqlUrl: content.request.gqlUrl, + graphQL: content.graphQL || false, + gRPC: content.gRPC || false, + network, + testContent: content.request.testContent, + }; + } + + if (network === 'grpc') { + requestFieldObj = { + ...newRequestFields, + method: content.request.method || 'GET', + protocol: content.protocol || 'http://', + url: content.url, + grpcUrl: content.request.grpcUrl, + graphQL: content.graphQL || false, + gRPC: content.gRPC || false, + network, + testContent: content.request.testContent, + }; + } + + let headerDeeperCopy; + + if (content.request.headers) { + headerDeeperCopy = JSON.parse(JSON.stringify(content.request.headers)); + headerDeeperCopy.push({ + id: content.request.headers.length + 1, + active: false, + key: '', + value: '', + }); + } + + let cookieDeeperCopy; + + if (content.request.cookies && !/ws/.test(protocol)) { + cookieDeeperCopy = JSON.parse(JSON.stringify(content.request.cookies)); + cookieDeeperCopy.push({ + id: content.request.cookies.length + 1, + active: false, + key: '', + value: '', + }); + } + + const requestHeadersObj = { + headersArr: headerDeeperCopy || [], + count: headerDeeperCopy ? headerDeeperCopy.length : 1, + }; + + const requestCookiesObj = { + cookiesArr: cookieDeeperCopy || [], + count: cookieDeeperCopy ? cookieDeeperCopy.length : 1, + }; + + const requestBodyObj = { + webrtcData: content.webrtcData, + bodyType: content.request.bodyType || 'raw', + bodyContent: content.request.body || '', + bodyVariables: content.request.bodyVariables || '', + rawType: content.request.rawType || 'Text (text/plain)', + JSONFormatted: true, + bodyIsNew: false, + }; + + dispatch(actions.setNewRequestFields(requestFieldObj)); + dispatch(actions.setNewRequestHeaders(requestHeadersObj)); + dispatch(actions.setNewRequestCookies(requestCookiesObj)); + dispatch(actions.setNewRequestBody(requestBodyObj)); + dispatch(actions.setNewRequestSSE(content.request.isSSE)); + + if (content && content.gRPC) { + const streamsDeepCopy = JSON.parse(JSON.stringify(content.streamsArr)); + const contentsDeepCopy = JSON.parse( + JSON.stringify(content.streamContent) + ); + + // construct the streams obj from passed in history content & set state in store + const requestStreamsObj = { + streamsArr: streamsDeepCopy, + count: content.queryArr.length, + streamContent: contentsDeepCopy, + selectedPackage: content.packageName, + selectedRequest: content.rpc, + selectedService: content.service, + selectedStreamingType: content.request.method, + initialQuery: content.initialQuery, + queryArr: content.queryArr, + protoPath: content.protoPath, + services: content.servicesObj, + protoContent: content.protoContent, + }; + + dispatch(actions.setNewRequestStreams(requestStreamsObj)); + } + + dispatch(actions.setSidebarActiveTab('composer')); + }; + + const removeReqRes = () => { + connectionController.closeReqRes(content); + reqResDelete(content); + }; + + const getBorderClass = () => { + let classes = 'highlighted-response '; + if (currentResponse.gRPC) classes += 'is-grpc-border'; + else if (currentResponse.graphQL) classes += 'is-graphQL-border'; + else if (currentResponse.request.method === 'WS') classes += 'is-ws-border'; + else if (currentResponse.webrtc) classes += 'is-webrtc-border'; + else classes += 'is-rest-border'; + return classes; + }; + + const highlightClasses = + currentResponse.id === content.id ? getBorderClass(currentResponse) : ''; + + return ( +
+ {/* TITLE BAR */} +
+
+ {request.method} +
+
+
{url}
+ {/* RENDER STATUS */} +
+ {connection === 'uninitialized' && ( +
+ )} + {connection === 'error' &&
} + {connection === 'open' &&
} + {connection === 'closed' && + method !== 'WS' && + method !== 'SUBSCRIPTION' && ( +
+ )} + {connection === 'closed' && + (method === 'WS' || method === 'SUBSCRIPTION') && ( +
+ )} +
+
+
+ {/* VIEW REQUEST DETAILS / MINIMIZE */} + {network !== 'ws' && ( +
{ + setShowDetails(showDetails === false); + }} + > + {showDetails === true && 'Hide Request Details'} + {showDetails === false && 'View Request Details'} + {network !== 'openapi' && showDetails === true && ( +
+ Copy to Composer +
+ )} +
+ )} + {/* REQUEST ELEMENTS */} + {showDetails === true && ( +
+ {network === 'rest' && ( + + )} + {network === 'openapi' && ( + + )} + {network === 'grpc' && ( + + )} + {network === 'graphQL' && ( + + )} + {network === 'webrtc' && } +
+ )} + {/* REMOVE / SEND BUTTONS */} +
+ + {/* SEND BUTTON */} + {connection === 'uninitialized' && ( + + )} + {/* VIEW RESPONSE BUTTON */} + {connection !== 'uninitialized' && ( + + )} +
+
+ ); +}; +export default SingleReqResContainer; diff --git a/src/client/components/containers/SingleScheduleReqResContainer.jsx b/src/client/components/containers/SingleScheduleReqResContainer.jsx index a59c650bb..58ab8a5f2 100644 --- a/src/client/components/containers/SingleScheduleReqResContainer.jsx +++ b/src/client/components/containers/SingleScheduleReqResContainer.jsx @@ -1,159 +1,132 @@ -import React, { useEffect, useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions.js"; - -import connectionController from "../../controllers/reqResController"; -import RestRequestContent from "../display/RestRequestContent.jsx"; -import GraphQLRequestContent from "../display/GraphQLRequestContent.jsx"; -import GRPCRequestContent from "../display/GRPCRequestContent.jsx"; -import ReqResCtrl from "../../controllers/reqResController"; - -const SingleScheduleReqResContainer = (props) => { - const [showDetails, setShowDetails] = useState(false); - const [checker, setChecker] = useState(false); - const dispatch = useDispatch(); - - const currentResponse = useSelector( - (store) => store.business.currentResponse - ); - - const newRequestFields = useSelector( - (store) => store.business.newRequestFields - ); - const newRequestStreams = useSelector( - (store) => store.business.newRequestStreams - ); - - const { - content, - content: { - id, - graphQL, - closeCode, - protocol, - request, - response, - connection, - connectionType, - isHTTP2, - url, - timeReceived, - timeSent, - rpc, - service, - }, - reqResUpdate, - reqResDelete, - index, - date, - } = props; - const network = content.request.network; - const method = content.request.method; - - const getBorderClass = () => { - let classes = "highlighted-response "; - if (currentResponse.gRPC) classes += "is-grpc-border"; - else if (currentResponse.graphQL) classes += "is-graphQL-border"; - else if (currentResponse.request.method === "WS") classes += "is-ws-border"; - else classes += "is-rest-border"; - return classes; - }; - - const highlightClasses = - currentResponse.id === content.id ? getBorderClass(currentResponse) : ""; - - //USE EFFECT - useEffect(() => { - dispatch( - actions.saveCurrentResponseData( - content, - "SingleScheduleReqResContainerComponent" - ) - ); - }, []); - - return ( -
- {/* TITLE BAR */} -
-
- {request.method} -
-
-
{url}
- {/* RENDER STATUS */} -
- {connection === "uninitialized" && ( -
- )} - {connection === "error" &&
} - {connection === "open" &&
} - {connection === "closed" && - method != "WS" && - method !== "SUBSCRIPTION" && ( -
- )} - {connection === "closed" && - (method === "WS" || method === "SUBSCRIPTION") && ( -
- )} -
-
-
- {/* VIEW REQUEST DETAILS / MINIMIZE */} - {network !== "ws" && ( -
{ - setShowDetails((showDetails = false)); - }} - > - {date} - {showDetails === true && ( -
- Copy to Composer -
- )} -
- )} - {/* REQUEST ELEMENTS */} - {showDetails === true && ( -
- {network === "rest" && ( - - )} - {network === "grpc" && ( - - )} - {network === "graphQL" && ( - - )} -
- )} - {/* REMOVE / SEND BUTTONS */} -
- {true && ( - - )} -
-
- ); -}; -export default SingleScheduleReqResContainer; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useEffect, useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions.js'; +import RestRequestContent from '../display/RestRequestContent.jsx'; +import GraphQLRequestContent from '../display/GraphQLRequestContent.jsx'; +import GRPCRequestContent from '../display/GRPCRequestContent.jsx'; + +const SingleScheduleReqResContainer = (props) => { + const [showDetails, setShowDetails] = useState(false); + const dispatch = useDispatch(); + + const currentResponse = useSelector( + (store) => store.business.currentResponse + ); + + const { + content, + content: { request, connection, isHTTP2, url }, + index, + date, + } = props; + const network = content.request.network; + const method = content.request.method; + + const getBorderClass = () => { + let classes = 'highlighted-response '; + if (currentResponse.gRPC) classes += 'is-grpc-border'; + else if (currentResponse.graphQL) classes += 'is-graphQL-border'; + else if (currentResponse.request.method === 'WS') classes += 'is-ws-border'; + else classes += 'is-rest-border'; + return classes; + }; + + const highlightClasses = + currentResponse.id === content.id ? getBorderClass(currentResponse) : ''; + + useEffect(() => { + dispatch( + actions.saveCurrentResponseData( + content, + 'SingleScheduleReqResContainerComponent' + ) + ); + }, [content, dispatch]); + + return ( +
+ {/* TITLE BAR */} +
+
+ {request.method} +
+
+
{url}
+ {/* RENDER STATUS */} +
+ {connection === 'uninitialized' && ( +
+ )} + {connection === 'error' &&
} + {connection === 'open' &&
} + {connection === 'closed' && + method != 'WS' && + method !== 'SUBSCRIPTION' && ( +
+ )} + {connection === 'closed' && + (method === 'WS' || method === 'SUBSCRIPTION') && ( +
+ )} +
+
+
+ {/* VIEW REQUEST DETAILS / MINIMIZE */} + {network !== 'ws' && ( +
{ + setShowDetails((showDetails = false)); + }} + > + {date} + {showDetails === true && ( +
+ Copy to Composer +
+ )} +
+ )} + {/* REQUEST ELEMENTS */} + {showDetails === true && ( +
+ {network === 'rest' && ( + + )} + {network === 'grpc' && ( + + )} + {network === 'graphQL' && ( + + )} +
+ )} + {/* REMOVE / SEND BUTTONS */} +
+ {true && ( + + )} +
+
+ ); +}; +export default SingleScheduleReqResContainer; diff --git a/src/client/components/containers/SingleTestContainer.jsx b/src/client/components/containers/SingleTestContainer.jsx index 7b449ac5f..e736334c0 100644 --- a/src/client/components/containers/SingleTestContainer.jsx +++ b/src/client/components/containers/SingleTestContainer.jsx @@ -1,95 +1,97 @@ -import React, {useState, useEffect} from 'react' -import { Doughnut } from "react-chartjs-2"; -import TestResult from './TestResult' -import VerticalProgress from '../display/testResultsAnimated' - -export default function SingleTestContainer({ currentResponse }) { - const { - request, - response - } = currentResponse; - - const { url } = currentResponse; - - - - const passFailScripts = []; - - let pass = 0; - let fail = 0; - - if (response.testResult !== undefined && response.testResult !== null) { - response.testResult.forEach((ele, idx) => { - if (ele.status === 'PASS') pass += 1; - else fail += 1; - const test = - - passFailScripts.push(test); - }); - } - - const data = { - datasets: [{data: [pass, fail], backgroundColor: ['#06a568', '#f66b61']}], - labels: ['Passed','Failed'], - }; - - - - - return ( - <> -
-
- Tests -
-
-
{url}
-
-
-
- {/*
{passFailScripts}
*/} -
-
Summary
-
-
-
- {/* Test Results Graph */} -
- -
-
- {/* Test Results Pass + Fail */} -
-
-
- Total Tests: {pass + fail} -
-
- Percentage Passed: {Math.floor((pass / (pass + fail)) * 100)}% -
-
-
- Passed: {pass} -
-
- Failed: {fail} -
-
-
-
{passFailScripts}
-
-
-
-
-
- - ) -} \ No newline at end of file +import React from 'react'; +import TestResult from './TestResult'; +import VerticalProgress from '../display/testResultsAnimated'; + +export default function SingleTestContainer({ currentResponse }) { + const { request, response } = currentResponse; + + const { url } = currentResponse; + + const passFailScripts = []; + + let pass = 0; + let fail = 0; + + if (response.testResult !== undefined && response.testResult !== null) { + response.testResult.forEach((ele, idx) => { + if (ele.status === 'PASS') pass += 1; + else fail += 1; + const test = ( + + ); + + passFailScripts.push(test); + }); + } + + const data = { + datasets: [{ data: [pass, fail], backgroundColor: ['#06a568', '#f66b61'] }], + labels: ['Passed', 'Failed'], + }; + + return ( + <> +
+
+ Tests +
+
+
+ {url} +
+
+
+
+ {/*
{passFailScripts}
*/} +
+
Summary
+
+
+
+ {/* Test Results Graph */} +
+ +
+
+ {/* Test Results Pass + Fail */} +
+
+
+ Total Tests:{' '} + {pass + fail} +
+
+ Percentage Passed:{' '} + + {Math.floor((pass / (pass + fail)) * 100)}% + +
+
+
+ + Passed: + {' '} + {pass} +
+
+ + Failed:{' '} + {' '} + {fail} +
+
+
+
{passFailScripts}
+
+
+
+
+
+ + ); +} diff --git a/src/client/components/containers/StoppedContainer.jsx b/src/client/components/containers/StoppedContainer.jsx index 9c1f0aa38..f7b479134 100644 --- a/src/client/components/containers/StoppedContainer.jsx +++ b/src/client/components/containers/StoppedContainer.jsx @@ -1,68 +1,74 @@ -import React, { useEffect, useState } from "react"; -import { connect, useDispatch } from "react-redux"; -import * as actions from "../../actions/actions"; -import SingleScheduleReqResContainer from "./SingleScheduleReqResContainer.jsx"; -import SingleReqResContainer from "./SingleReqResContainer.jsx"; -import ReqResCtrl from "../../controllers/reqResController"; - -const mapStateToProps = (store) => ({ - reqResArray: store.business.reqResArray, - scheduledReqResArray: store.business.scheduledReqResArray, - currentTab: store.business.currentTab, -}); - -const mapDispatchToProps = (dispatch) => ({ - reqResDelete: (reqRes) => { - dispatch(actions.reqResDelete(reqRes)); - }, - reqResUpdate: (reqRes) => { - dispatch(actions.reqResUpdate(reqRes)); - }, - scheduledReqResDelete: () => { - dispatch(actions.scheduledReqResDelete()); - }, - clearAllGraph: () => { - dispatch(actions.clearAllGraph()); - }, -}); - -const StoppedContainer = (props) => { - const { reqResArray, reqResDelete, reqResUpdate, runScheduledTests, scheduledReqResArray, scheduledReqResDelete, clearAllGraph } = props; - const dispatch = useDispatch(); - - let scheduledReqResMapped = scheduledReqResArray.map((reqRes, index) => { - return ( - - ); - }); - - return ( - <> -
-
Scheduled Requests
-
- -
- {scheduledReqResMapped.reverse()} -
- - ); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(StoppedContainer); +import React from 'react'; +import { connect, useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions'; +import SingleScheduleReqResContainer from './SingleScheduleReqResContainer.jsx'; + +const mapStateToProps = (store) => ({ + reqResArray: store.business.reqResArray, + scheduledReqResArray: store.business.scheduledReqResArray, + currentTab: store.business.currentTab, +}); + +const mapDispatchToProps = (dispatch) => ({ + reqResDelete: (reqRes) => { + dispatch(actions.reqResDelete(reqRes)); + }, + reqResUpdate: (reqRes) => { + dispatch(actions.reqResUpdate(reqRes)); + }, + scheduledReqResDelete: () => { + dispatch(actions.scheduledReqResDelete()); + }, + clearAllGraph: () => { + dispatch(actions.clearAllGraph()); + }, +}); + +const StoppedContainer = (props) => { + const { + reqResArray, + reqResDelete, + reqResUpdate, + runScheduledTests, + scheduledReqResArray, + scheduledReqResDelete, + clearAllGraph, + } = props; + const dispatch = useDispatch(); + + const scheduledReqResMapped = scheduledReqResArray.map((reqRes, index) => { + return ( + + ); + }); + + return ( + <> +
+
Scheduled Requests
+
+ +
+ {scheduledReqResMapped.reverse()} +
+ + ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(StoppedContainer); diff --git a/src/client/components/containers/TestResult.jsx b/src/client/components/containers/TestResult.jsx index 1abc0dae8..d3a2ec00d 100644 --- a/src/client/components/containers/TestResult.jsx +++ b/src/client/components/containers/TestResult.jsx @@ -1,19 +1,22 @@ -import React from 'react'; - -export default function TestResult({ id, status, message }) { - const testColor = status === 'PASS' ? 'success' : 'danger'; - - return ( -
-
- {status} -
-
- {message} -
-
- ); -} +import React from 'react'; + +function TestResult({ id, status, message }) { + const testColor = status === 'PASS' ? 'success' : 'danger'; + + return ( +
+
+ {status} +
+
+ {message} +
+
+ ); +} + +export default TestResult; diff --git a/src/client/components/containers/TestsContainer.jsx b/src/client/components/containers/TestsContainer.jsx index bad12dd9c..9519e3682 100644 --- a/src/client/components/containers/TestsContainer.jsx +++ b/src/client/components/containers/TestsContainer.jsx @@ -1,13 +1,15 @@ -import React from "react"; -import EmptyState from "../display/EmptyState"; -import SingleTestContainer from './SingleTestContainer'; - -export default function TestsContainer({ currentResponse }) { - return ( - currentResponse.response && - currentResponse.response.testResult && - currentResponse.response.testResult.length > 0 ? - - : - ); -} +import React from 'react'; +import EmptyState from '../display/EmptyState'; +import SingleTestContainer from './SingleTestContainer'; + +function TestsContainer({ currentResponse }) { + return currentResponse.response && + currentResponse.response.testResult && + currentResponse.response.testResult.length > 0 ? ( + + ) : ( + + ); +} + +export default TestsContainer; diff --git a/src/client/components/containers/UpdatePopUpContainer.jsx b/src/client/components/containers/UpdatePopUpContainer.jsx index 12b208038..fb82498ef 100644 --- a/src/client/components/containers/UpdatePopUpContainer.jsx +++ b/src/client/components/containers/UpdatePopUpContainer.jsx @@ -1,43 +1,47 @@ -import React, { useState, useEffect } from "react"; -import { connect } from "react-redux"; - -const { api } = window; - -const UpdatePopUpContainer = ({ message, setMessage }) => { - - useEffect(()=>{ - api.receive("message", (e, text) => { - console.log("AUTO-UPDATER STATUS: " + e); - if (text) setMessage(text); - }); - }); - - const handleUpdateClick = () => { - api.send("quit-and-install"); - setMessage(null); - }; - - return message ? ( -
- {message} - {message === "Update downloaded." && ( - <> - - Do you want to restart and install now? (If not, will - auto-install on restart.) - - - )} - - {message === "Update downloaded." && ( - - )} -
- ) : null; -}; - -export default UpdatePopUpContainer; +import React, { useEffect } from 'react'; + +const { api } = window; + +const UpdatePopUpContainer = ({ message, setMessage }) => { + useEffect(() => { + api.receive('message', (e, text) => { + console.log('AUTO-UPDATER STATUS: ' + e); + if (text) setMessage(text); + }); + }); + + const handleUpdateClick = () => { + api.send('quit-and-install'); + setMessage(null); + }; + + return message ? ( +
+ {message} + {message === 'Update downloaded.' && ( + <> + + Do you want to restart and install now? (If not, will auto-install + on restart.) + + + )} + + {message === 'Update downloaded.' && ( + + )} +
+ ) : null; +}; + +export default UpdatePopUpContainer; diff --git a/src/client/components/containers/ViewRequestDetails.jsx b/src/client/components/containers/ViewRequestDetails.jsx deleted file mode 100644 index a8951c9a8..000000000 --- a/src/client/components/containers/ViewRequestDetails.jsx +++ /dev/null @@ -1,6 +0,0 @@ -//AL: what was the purpose of this one? -import React from "react"; - -export default function ViewRequestDetails() { - return
; -} diff --git a/src/client/components/containers/WorkspaceContainer.jsx b/src/client/components/containers/WorkspaceContainer.jsx index 98c21dba6..584acb2bf 100644 --- a/src/client/components/containers/WorkspaceContainer.jsx +++ b/src/client/components/containers/WorkspaceContainer.jsx @@ -1,44 +1,44 @@ -import React, { useState } from "react"; - -import ReqResCtrl from "../../controllers/reqResController.js"; -import SaveWorkspaceModal from "./SaveWorkspaceModal"; -import ReqResContainer from "./ReqResContainer.jsx"; - -export default function WorkspaceContainer() { - // LOCAL STATE HOOKS - const [showModal, setShowModal] = useState(false); - - return ( -
- {/* NAV BAR */} -
- - - -
- - - {/* REQUEST CARDS */} - -
- ); -} +import React, { useState } from 'react'; +import ReqResCtrl from '../../controllers/reqResController.js'; +import ReqResContainer from './ReqResContainer.jsx'; +import SaveWorkspaceModal from './SaveWorkspaceModal'; + +function WorkspaceContainer() { + const [showModal, setShowModal] = useState(false); + + return ( +
+ {/* NAV BAR */} +
+ + + +
+ + + {/* REQUEST CARDS */} + +
+ ); +} + +export default WorkspaceContainer; diff --git a/src/client/components/display/BarGraph.jsx b/src/client/components/display/BarGraph.jsx index 469e5bd16..a30cddbbb 100644 --- a/src/client/components/display/BarGraph.jsx +++ b/src/client/components/display/BarGraph.jsx @@ -1,220 +1,221 @@ -import React, { useState, useEffect } from "react"; -import { connect } from "react-redux"; -import { HorizontalBar, Line } from "react-chartjs-2"; -import * as store from "../../store"; -import * as actions from "../../actions/actions"; - -//necessary for graph styling due to CSP -Chart.platform.disableCSSInjection = true; - -const mapStateToProps = (store) => ({ - dataPoints: store.business.dataPoints, - currentResponse: store.business.currentResponse, -}); - -const mapDispatchToProps = (dispatch) => ({ - saveCurrentResponseData: (reqRes) => { - dispatch(actions.saveCurrentResponseData(reqRes, "bargraph")); - }, - updateGraph: (reqRes) => { - dispatch(actions.updateGraph(reqRes)); - }, - clearGraph: (id) => { - store.default.dispatch(actions.clearGraph(id)); - }, -}); - -const BarGraph = (props) => { - const { dataPoints, currentResponse } = props; - - const [chartURL, setChartURL] = useState(""); - const [host, setHost] = useState(null); - - //state for showing graph, depending on whether there are datapoints or not. - //must default to true, because graph will not render if initial container's display is 'none' - const [show, toggleShow] = useState(true); - //Default state for chart data - const [chartData, updateChart] = useState({ - labels: [], - datasets: [ - { - data: [], - }, - ], - }); - - //default state for chart options - const [chartOptions, updateOptions] = useState({ - scales: { - yAxes: [ - { - scaleLabel: { - display: true, - labelString: chartURL, - }, - ticks: { - beginAtZero: true, - }, - }, - ], - xAxes: [ - { - ticks: { - beginAtZero: true, - }, - }, - ], - }, - // animation: { - // duration: 50, //buggy animation, get rid of transition - // }, - maintainAspectRatio: false, - }); - - //helper function that returns chart data object - const dataUpdater = (labelArr, timesArr, BGsArr, bordersArr, reqResArr) => { - return { - labels: labelArr, - datasets: [ - { - label: "Response Time", - data: timesArr, - backgroundColor: BGsArr, - borderColor: bordersArr, - borderWidth: 1, - fill: false, - lineTension: 0, - maxBarThickness: 300, - reqRes: reqResArr, - }, - ], - }; - }; - - //helper function that returns chart options object - const optionsUpdater = (arr) => { - return { - scales: { - yAxes: [ - { - scaleLabel: { - display: true, //boolean - labelString: chartURL, - }, - ticks: { - beginAtZero: true, - }, - }, - ], - xAxes: [ - { - // scaleLabel: { - // display: true, - // }, - }, - ], - }, - // animation: { - // duration: 50, - // }, - maintainAspectRatio: false, //necessary for keeping chart within container - }; - }; - - // click handling to load response data in the response panel - const getElementAtEvent = (element) => { - if (!element.length) return; - // get the response data corresponding to the clicked element - const index = element[0]._index; - const reqResToSend = - element[0]._chart.config.data.datasets[0].reqRes[index]; - // send the data to the response panel - props.saveCurrentResponseData(reqResToSend); - }; - - useEffect(() => { - const { id, host } = currentResponse; - setHost(host?.slice(7)); - - let url; - let urls; - let times; - let BGs; - let borders; - let reqResObjs; - if (dataPoints[id]?.length) { - const point = dataPoints[id][0]; - // // if grpc, just return the server IP - if (point.reqRes.gRPC) url = `${point.url}`; - // if point.url is lengthy, just return the domain and the end of the uri string - const domain = point.url - .replace("http://", "") - .replace("https://", "") - .split(/[/?#]/)[0]; - url = `${domain} ${ - point.url.length > domain.length + 8 - ? `- ..${point.url.slice(point.url.length - 8, point.url.length)}` - : "" - }`; - - setChartURL(url); - - //extract arrays from data point properties to be used in chart data/options that take separate arrays - urls = dataPoints[id].map((point, index) => index + 1); - times = dataPoints[id].map( - (point) => point.timeReceived - point.timeSent - ); - BGs = dataPoints[id].map((point) => "rgba(" + point.color + ", 0.2)"); - borders = dataPoints[id].map((point) => "rgba(" + point.color + ", 1)"); - reqResObjs = dataPoints[id].map((point) => point.reqRes); - //show graph upon receiving data points - toggleShow(true); - } else { - setHost(null); - //hide graph when no data points - toggleShow(false); - } - //update state with updated dataset - updateChart(dataUpdater(urls, times, BGs, borders, reqResObjs)); - //conditionally update options based on length of dataPoints array - - updateOptions(optionsUpdater(dataPoints[id])); - }, [dataPoints, currentResponse, chartURL]); - - // useEffect(updateGraph(currentResponse), [currentResponse]) - - const chartClass = show ? "chart" : "chart-closed"; - - return ( -
-
- -
-
- -
-
- ); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(BarGraph); +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Line } from 'react-chartjs-2'; +import * as store from '../../store'; +import * as actions from '../../actions/actions'; + +//necessary for graph styling due to CSP +Chart.platform.disableCSSInjection = true; + +const mapStateToProps = (store) => ({ + dataPoints: store.business.dataPoints, + currentResponse: store.business.currentResponse, +}); + +const mapDispatchToProps = (dispatch) => ({ + saveCurrentResponseData: (reqRes) => { + dispatch(actions.saveCurrentResponseData(reqRes, 'bargraph')); + }, + updateGraph: (reqRes) => { + dispatch(actions.updateGraph(reqRes)); + }, + clearGraph: (id) => { + store.default.dispatch(actions.clearGraph(id)); + }, +}); + +const BarGraph = (props) => { + const { dataPoints, currentResponse } = props; + + const [chartURL, setChartURL] = useState(''); + const [host, setHost] = useState(null); + + //state for showing graph, depending on whether there are data points or not. + //must default to true, because graph will not render if initial container's display is 'none' + const [show, toggleShow] = useState(true); + //Default state for chart data + const [chartData, updateChart] = useState({ + labels: [], + datasets: [ + { + data: [], + }, + ], + }); + + //default state for chart options + const [chartOptions, updateOptions] = useState({ + scales: { + yAxes: [ + { + scaleLabel: { + display: true, + labelString: chartURL, + }, + ticks: { + beginAtZero: true, + }, + }, + ], + xAxes: [ + { + ticks: { + beginAtZero: true, + }, + }, + ], + }, + // animation: { + // duration: 50, //buggy animation, get rid of transition + // }, + maintainAspectRatio: false, + }); + + //helper function that returns chart data object + const dataUpdater = (labelArr, timesArr, BGsArr, bordersArr, reqResArr) => { + return { + labels: labelArr, + datasets: [ + { + label: 'Response Time', + data: timesArr, + backgroundColor: BGsArr, + borderColor: bordersArr, + borderWidth: 1, + fill: false, + lineTension: 0, + maxBarThickness: 300, + reqRes: reqResArr, + }, + ], + }; + }; + + //helper function that returns chart options object + const optionsUpdater = (arr) => { + return { + scales: { + yAxes: [ + { + scaleLabel: { + display: true, //boolean + labelString: chartURL, + }, + ticks: { + beginAtZero: true, + }, + }, + ], + xAxes: [ + { + // scaleLabel: { + // display: true, + // }, + }, + ], + }, + // animation: { + // duration: 50, + // }, + maintainAspectRatio: false, //necessary for keeping chart within container + }; + }; + + // click handling to load response data in the response panel + const getElementAtEvent = (element) => { + if (!element.length) return; + // get the response data corresponding to the clicked element + const index = element[0]._index; + const reqResToSend = + element[0]._chart.config.data.datasets[0].reqRes[index]; + // send the data to the response panel + props.saveCurrentResponseData(reqResToSend); + }; + + useEffect(() => { + const { id, host } = currentResponse; + setHost(host?.slice(7)); + + let url; + let urls; + let times; + let BGs; + let borders; + let reqResObjs; + if (dataPoints[id]?.length) { + const point = dataPoints[id][0]; + // if grpc, just return the server IP + if (point.reqRes.gRPC) url = `${point.url}`; + // if point.url is lengthy, just return the domain and the end of the uri string + const domain = point.url + .replace('http://', '') + .replace('https://', '') + .split(/[/?#]/)[0]; + url = `${domain} ${ + point.url.length > domain.length + 8 + ? `- ..${point.url.slice(point.url.length - 8, point.url.length)}` + : '' + }`; + + setChartURL(url); + + //extract arrays from data point properties to be used in chart data/options that take separate arrays + urls = dataPoints[id].map((point, index) => index + 1); + times = dataPoints[id].map( + (point) => point.timeReceived - point.timeSent + ); + BGs = dataPoints[id].map((point) => 'rgba(' + point.color + ', 0.2)'); + borders = dataPoints[id].map((point) => 'rgba(' + point.color + ', 1)'); + reqResObjs = dataPoints[id].map((point) => point.reqRes); + //show graph upon receiving data points + toggleShow(true); + } else { + setHost(null); + //hide graph when no data points + toggleShow(false); + } + //update state with updated dataset + updateChart(dataUpdater(urls, times, BGs, borders, reqResObjs)); + //conditionally update options based on length of dataPoints array + + updateOptions(optionsUpdater(dataPoints[id])); + }, [dataPoints, currentResponse, chartURL, optionsUpdater]); + + // useEffect(updateGraph(currentResponse), [currentResponse]) + + const chartClass = show ? 'chart' : 'chart-closed'; + + return ( +
+
+ +
+
+ +
+
+ ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(BarGraph); diff --git a/src/client/components/display/ClearHistoryBtn.jsx b/src/client/components/display/ClearHistoryBtn.jsx index a181e28c4..36d978f7b 100644 --- a/src/client/components/display/ClearHistoryBtn.jsx +++ b/src/client/components/display/ClearHistoryBtn.jsx @@ -1,31 +1,32 @@ -import React, { useEffect } from "react"; -import historyController from "../../controllers/historyController"; - -// utilizing API we created in preload.js for node-free IPC communication -const { api } = window; - -const ClearHistoryBtn = (props) => { - // cleanup api.receive event listener on dismount - useEffect(() => { - api.receive("clear-history-response", (res) => { - // a response of 0 from main means user has selected 'confirm' - if (res.response === 0) { - historyController.clearHistoryFromIndexedDb(); - props.clearHistory(); - } - }); - }, []); - - const handleClick = () => { - api.send("confirm-clear-history"); - }; - return ( - - ); -}; - -export default ClearHistoryBtn; +import React, { useEffect } from 'react'; +import historyController from '../../controllers/historyController'; + +// utilizing API we created in preload.js for node-free IPC communication +const { api } = window; + +const ClearHistoryBtn = (props) => { + // cleanup api.receive event listener on dismount + useEffect(() => { + api.receive('clear-history-response', (res) => { + // a response of 0 from main means user has selected 'confirm' + if (res.response === 0) { + historyController.clearHistoryFromIndexedDb(); + props.clearHistory(); + } + }); + }, []); + + const handleClick = () => { + api.send('confirm-clear-history'); + }; + return ( + + ); +}; + +export default ClearHistoryBtn; diff --git a/src/client/components/display/CloseBtn.jsx b/src/client/components/display/CloseBtn.jsx deleted file mode 100644 index cf569c9e9..000000000 --- a/src/client/components/display/CloseBtn.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import ReqResCtrl from '../../controllers/reqResController'; - -const CloseBtn = ({ stylesObj, content }) => { - return ( - - ) -}; - -export default CloseBtn; diff --git a/src/client/components/display/Collection.jsx b/src/client/components/display/Collection.jsx index 7a3065a4d..c118b0bbf 100644 --- a/src/client/components/display/Collection.jsx +++ b/src/client/components/display/Collection.jsx @@ -1,44 +1,54 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; -import * as actions from '../../actions/actions'; -import collectionsController from '../../controllers/collectionsController'; - -const Collection = (props) => { - const dispatch = useDispatch(); - const setWorkspaceTab = (tabName) => dispatch(actions.setWorkspaceActiveTab(tabName)); - - const addCollectionToReqResContainer = () => { - props.collectionToReqRes(props.content.reqResArray); - setWorkspaceTab('workspace'); - } - - const deleteCollection = (e) => { - props.deleteFromCollection(props.content); //a function we need to make in the container - collectionsController.deleteCollectionFromIndexedDb(e.target.id); - } - - return ( -
- -
-
- {props.content.name} -
-
-
collectionsController.exportCollection(props.content.id)}> - Export -
-
-
-
-
- -
-
- ); -} - -export default Collection; \ No newline at end of file +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import * as actions from '../../actions/actions'; +import collectionsController from '../../controllers/collectionsController'; + +const Collection = (props) => { + const dispatch = useDispatch(); + const setWorkspaceTab = (tabName) => + dispatch(actions.setWorkspaceActiveTab(tabName)); + + const addCollectionToReqResContainer = () => { + props.collectionToReqRes(props.content.reqResArray); + setWorkspaceTab('workspace'); + }; + + const deleteCollection = (e) => { + props.deleteFromCollection(props.content); + collectionsController.deleteCollectionFromIndexedDb(e.target.id); + }; + + return ( +
+
+
+ {props.content.name} +
+
+
+ collectionsController.exportCollection(props.content.id) + } + > + Export +
+
+
+
+ +
+
+ ); +}; + +export default Collection; diff --git a/src/client/components/display/ContentReqRow.jsx b/src/client/components/display/ContentReqRow.jsx index 446d67335..6deff7d88 100644 --- a/src/client/components/display/ContentReqRow.jsx +++ b/src/client/components/display/ContentReqRow.jsx @@ -1,10 +1,24 @@ -import React from 'react' - -export default function ContentReqRow({ data }) { - return ( -
- - -
- ) -} +import React from 'react'; + +function ContentReqRow({ data }) { + return ( +
+ + +
+ ); +} + +export default ContentReqRow; diff --git a/src/client/components/display/CookieTable.tsx b/src/client/components/display/CookieTable.tsx deleted file mode 100644 index 33a88c91b..000000000 --- a/src/client/components/display/CookieTable.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import CookieTableRow from "./CookieTableRow"; -import { CookieProps } from "../../../types"; - -const CookieTable = ({ cookies }: CookieProps) => { - let cookieRowArray: Array; - if (Array.isArray(cookies)) { - cookieRowArray = cookies.map((cookie, i) => { - return ( - - ); - }); - } - return( -
-
-
Name
-
ExpirationDate
-
Domain
-
HostOnly
-
Path
-
Secure
-
HttpOnly
-
Session
-
Value
-
- {cookieRowArray} -
- ) -} - -export default CookieTable; diff --git a/src/client/components/display/CookieTableCell.tsx b/src/client/components/display/CookieTableCell.tsx deleted file mode 100644 index dc7935c64..000000000 --- a/src/client/components/display/CookieTableCell.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from "react"; -import { CookieProps } from "../../../types"; - -const CookieTableCell = ({ detail }: CookieProps) => { - return ( -
{detail.toString()}
- ); -} - -export default CookieTableCell; diff --git a/src/client/components/display/CookieTableRow.jsx b/src/client/components/display/CookieTableRow.jsx deleted file mode 100644 index 0f9c92b7b..000000000 --- a/src/client/components/display/CookieTableRow.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from "react"; -import CookieTableCell from "./CookieTableCell"; - -const CookieTableRow = ({ cookies }) => { - - const tableCellArray = []; - for (const key in cookies) { - tableCellArray.push( - - ); - } - if (!cookies.expirationDate) { - tableCellArray.push(); - } - return ( -
{tableCellArray}
- ) -} - -export default CookieTableRow; diff --git a/src/client/components/display/EmptyState.jsx b/src/client/components/display/EmptyState.jsx index acf3a02c9..be6aca3b4 100644 --- a/src/client/components/display/EmptyState.jsx +++ b/src/client/components/display/EmptyState.jsx @@ -1,12 +1,12 @@ -import React from 'react' -import logofaded from '../../../assets/img/swell-logo-faded.png' - -function EmptyState({ connection }) { - return ( -
- faded-logo -
- ) -} - -export default EmptyState +import React from 'react'; +import logofaded from '../../../assets/img/swell-logo-faded.png'; + +function EmptyState({ connection }) { + return ( +
+ faded-logo +
+ ); +} + +export default EmptyState; diff --git a/src/client/components/display/EventPreview.jsx b/src/client/components/display/EventPreview.jsx index f5d07bd01..3961aab38 100644 --- a/src/client/components/display/EventPreview.jsx +++ b/src/client/components/display/EventPreview.jsx @@ -1,63 +1,60 @@ -import React, { useState } from "react"; -import dropDownArrow from "../../../assets/icons/caret-down-tests.svg"; -import dropDownArrowUp from "../../../assets/icons/caret-up-tests.svg"; - -const EventPreview = ({ contents }) => { - - const [showPreview, setShowPreview] = useState(false); - const handleShowPreview = () => setShowPreview(!showPreview); - - return ( -
-
{ - handleShowPreview(); - }} - > - {showPreview === true && - <> - Hide Preview - - - - - } - {showPreview === false && - <> - View Preview - - - - - } -
- {showPreview === true && -
-