diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1c3e9b9ec..5bf240180 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,6 +30,23 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + + - name: Use Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18.20.6 # Or your preferred 18.x version + + - name: Print Node.js version + run: node -v + + - name: Install dependencies + run: npm ci # Or npm install if you prefer + + - name: Build project + run: | + make boostrap + make release + npm run build # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -43,19 +60,15 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # - name: Autobuild + # uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release + # uses a compiled language (we moved the "below" lines up to 42 and edited them) - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/createPackages.yml b/.github/workflows/createPackages.yml index 6451f1da5..43c43f881 100644 --- a/.github/workflows/createPackages.yml +++ b/.github/workflows/createPackages.yml @@ -9,16 +9,16 @@ jobs: tests: strategy: matrix: - node-version: [16] + node-version: [18.20.6] os: [macos-latest, ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js 18.20.6 # this used to be dynamically updated, but kept pulling an old version number. you'll have to manually update these throughout this file to be safe. uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 18.20.6 - name: Install dependencies run: npm i && npm ci - name: Run unit tests @@ -40,15 +40,15 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16.15' + node-version: '18.20.6' - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: - tag_name: v1.16.0 # Replace with your desired tag or version number - release_name: Release v1.16.0 # Replace with your desired release name + tag_name: v1.19.0 # Replace with your desired tag or version number + release_name: Release v1.19.0 # Replace with your desired release name draft: true body: | @@ -67,7 +67,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16.15' + node-version: '18.20.6' - name: Install Dependencies run: npm install @@ -89,7 +89,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16.15' # Use the specific version of Node that your project requires + node-version: '18.20.6' # Use the specific version of Node that your project requires - name: Install Dependencies run: npm install @@ -118,7 +118,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16.15' # Use the specific version of Node that your project requires + node-version: '18.20.6' # Use the specific version of Node that your project requires - name: Install Dependencies run: npm install diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index c6c1dea01..7ad54fa6a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,7 +6,7 @@ jobs: tests: strategy: matrix: - node-version: [16] + node-version: [18.20.6] os: [macos-latest, ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -18,7 +18,11 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm i && npm ci + - name: Apply patches + env: + NODE_ENV: --no-node-snapshot + run: npx patch-package - name: Run unit tests uses: coactions/setup-xvfb@v1 with: - run: npm run test-jest \ No newline at end of file + run: npm run test-jest diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 000000000..dd6134d60 --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,6 @@ +From node:18.20 +RUN npm install -g webpack +Workdir /app +Copy package*.json /app +Run npm install +Expose 8080 \ No newline at end of file diff --git a/README.md b/README.md index 95e429273..52ebff3e5 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Swell is a one-stop shop for sending and monitoring your API requests: - Send and monitor streams over HTTP/2 (including SSEs) and WebSockets - Create GraphQL queries, introspections, mutations, and subscriptions +- Test WebRTC applications over video, audio and text channels - Stress testing HTTP/2 and GraphQL endpoints - Create your own HTTP/2 mock server - Store workspaces of multiple requests for later use @@ -67,6 +68,34 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W See [tRPC docs](https://trpc.io/docs/) for more information on sending tRPC requests or setting up a tRPC server. + +- _WebRTC_: Swell makes it easy to test WebRTC applications for video, audio and text channels. Currently Swell supports manual entry of SDPs. + + ### Walkthrough for setting up a text channel connection using the app's generated offer and answer: + + - Step 1 + Caller: Generate an offer by clicking “Get Offer.” Copy the offer to your computer's clipboard and send it to recipient (we recommend sending by email). + - Step 2 + Recipient: Copy the offer you received from the caller and paste it into the offer box (the top text box) + - Step 3 + Recipient: Click “Get answer” button, generate an answer and copy it to your computer's clipboard. Send it to caller (email recommended) + - Step 4 + Caller: Copy answer to your computer's clipboard and paste it into the answer box (bottom text box). + - Step 5 + Caller: Click the “add answer” button. Now the connection is open! + - Step 6 + Caller: Click “add to workspace” button. + - Step 7 + Recipient: Click “add to workspace” button. + - Step 8 + Caller: Click "Send" button on the left-hand side of the app. + - Step 9 + Recipient: Click "Send" button on the left-hand side of the app. + - Step 10 + Send and receive text messages via the response panel at the bottom of the app. + + + ## Additional features - _Stress testing for HTTP/2 and GraphQL_: Test your server backend with Swell's stress testing feature to ensure your server can manage expected and unexpected loads accordingly @@ -91,6 +120,7 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W + ## Experimental Features - _Mock Server_: Swell allows you to create your own HTTP/2 mock server to facilitate front-end development without depending on a fully built backend server. @@ -98,9 +128,6 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W - _Webhooks_: Swell includes user-defined HTTP callback connection testing designed to test other server's connection to the web and ability to send data. The test insures that when an event occurs, the source site makes an HTTP request to the URL configured for the webhook. -- _WebRTC_: Swell makes it easy to test WebRTC applications for both video and text channels. Currently Swell supports manual entry of SDPs. - - - _OpenAPI_: Swell supports the enumeration and execution of REST and RPC API requests as defined in a user-provided OpenAPI document. @@ -110,7 +137,7 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W - React - React Router - Material UI -- Redux +- Redux Toolkit - Apollo Client - Websockets - gRPC-js @@ -124,6 +151,12 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W - Playwright ## Authors + +- **Isaac Mbambo** - [IM236](https://github.com/IM236) +- **Kiki Hunt** - [Iloveeverything](https://github.com/Iloveeverything) +- **Ting Lee** - [tingEng](https://github.com/tingEng) +- **Rachel Dean** - [rchldn](https://github.com/rchldn) +- **Kadeem Reid** - [Kadeem929](https://github.com/Kadeem929) - **Karol Krzywon** - [kkrzywon](https://github.com/kkrzywon) - **Howard Sun** - [howardCodeGit](https://github.com/howardCodeGit) - **Carter Sarkela** - [CarterSarkela](https://github.com/CarterSarkela) @@ -215,4 +248,4 @@ We highly encourage you to check out the `DEV-README.md` in the `docs` folder. W ## License -This project is licensed under the MIT License +This project is licensed under the MIT License \ No newline at end of file diff --git a/build/config.gypi b/build/config.gypi index 71c5bb224..20f7f8776 100644 --- a/build/config.gypi +++ b/build/config.gypi @@ -1,7 +1,7 @@ # Do not edit. File was generated by node-gyp's "configure" step { "target_defaults": { - "cflags": [], + "cflags": ['-std=c++20'], "default_configuration": "Release", "defines": [], "include_dirs": [], @@ -52,7 +52,7 @@ "node_use_dtrace": "true", "node_use_etw": "false", "node_use_node_code_cache": "true", - "node_use_node_snapshot": "true", + "node_use_node_snapshot": "false", "node_use_openssl": "true", "node_use_v8_platform": "true", "node_with_ltcg": "false", diff --git a/docs/DEV-README.md b/docs/DEV-README.md index e869a175a..d8ac783f4 100644 --- a/docs/DEV-README.md +++ b/docs/DEV-README.md @@ -19,7 +19,7 @@ Thank you for your consideration and let's work together on making Swell one of - TypeScript + JavaScript - React -- Redux +- Redux Toolkit - SASS - Node - Express @@ -43,10 +43,23 @@ Thank you for your consideration and let's work together on making Swell one of ## How to download and test the application locally? 1. Fork and/or clone the repository into your local machine -2. In your terminal: +2. Update dependencies + - `isolated-vm` combined fix: There is a problem with `vm2` (a sandbox); it has vulnerabilities and was discontinued. An alternative is `isolated-vm` which uses the chromium browser's v8 engine. The prior version of `isolated-vm` on Swell's package.json was only compatible with Node 16. In order to upgrade to the latest version we ran the commands as + - We are using Node 18 instead of the most recent version of Node because of C++ compatibility with the Xcode command line. + - `node-gyp` is a tool for compiling native add-on modules for Node.js. It is often used when installing certain npm packages that require native code compilation. + + 1. If you have them, delete node modules, package-lock.json and dist folders + 2. `cd` into User directory; + 3. `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash` (installing nvm package so you can manage versions, and making it so you can run `nvm install` command inside of the terminal) + 4. While still in user folder: `source ~/.bashrc # or ~/.zshrc or ~/.bash_profile` + 5. `vm install 18` + 6. `nvm use 18` + 7. `npm install -g node-gyp` + +3. In your terminal: - `npm install`, then - `npm run dev` -3. Wait for the electron application to start up (it may take a bit) +4. Wait for the electron application to start up (it may take a bit) There is E2E testing available via `npm run test`. Note that not all tests in the E2E test suite work currently. Please refer to `./test/testSuite.js` and `./test/subSuites` for more details. @@ -111,6 +124,7 @@ From a functionality standpoint: - HTTP/2 stress testing with `GET` requests - GraphQL stress testing with `Query` - Mock server for HTTP/2 (`Express`) +- WebRTC testing for text, video, and audio channel connections - Ability to store historical requests and create/delete workspaces - Frontend conversion to TypeScript - From a codebase standpoint: @@ -140,9 +154,9 @@ endeavour. The impacts to the product are: As you iterate the product, keep in mind the footprint your new feature(s) could add to the codebase. Could you re-use some of the existing modules? Can you even refactor and/or remove the obsolete code to help maintain the health of the codebase? -There are many parts of the codebase that break DRY principles, and with such a large application, really keep in mind that when you add features to ask if it is completely necessary. Past iterators added an experimental feature(s) without it fully working and the following team(s) would add their own experimental feature. Fixing features the past teams couldn't get to is not only a great way to learn these technologies but also a great thing to talk about in interviews. "I fixed the webRTC feature that has been stagnant for 5 years", "I addressed the technical debt and reorganized the state...", or "Increased the quality of typeScript". These all show maturity as a developer and will allow us to focus the entire time of OSP on the final 20% problems. +There are many parts of the codebase that break DRY principles, and with such a large application, really keep in mind when you add features to ask if it is completely necessary. Past iterators added an experimental feature(s) without it fully working and the following team(s) would add their own experimental feature. Fixing features the past teams couldn't get to is not only a great way to learn these technologies but also a great thing to talk about in interviews. "I fixed the webRTC feature that has been stagnant for 5 years", "I addressed the technical debt and reorganized the state...", or "Increased the quality of typeScript". These all show maturity as a developer and will allow us to focus the entire time of OSP on the final 20% problems. -Legacy Components - As a part of a clean up effort, all files that are no longer being used have been moved to the legacy component folder. Examples of these files come from the migration to shared components. The original location of the components is mentioned in the comments of the relocated files. +Legacy Components - As a part of a cleanup effort, all files that are no longer being used have been moved to the legacy component folder. Examples of these files come from the migration to shared components. The original location of the components is mentioned in the comments of the relocated files. ### _Ensure consistent redux state management_ @@ -212,12 +226,34 @@ Currently, the HTTP/2 mock server has the ability to create a server that is acc In a recent iteration, the WebRTC feature was changed from STUN Server testing to Client RTC Connection testing, allowing Swell to test if another client is able to create an RTC connection to transmit text and video data. Because the RTCPeerConnection has to be initiated before we generate the SDP, this connection is set up differently from the other networks. Other networks purely need the primitive strings as input, and the response is created on click of `Send` (in the workspace panel). For WebRTC, the connection object is created as an input, and when the user clicks `Send` then the data transmitted data is allowed to be displayed (although data is being transmitted through the connection even before `Send` is clicked). This means the WebRTC ReqRes can't really be saved in history or re-connected beyond the first connection. -Areas for improvement: +In the repo’s [excalidraw](https://excalidraw.com/#room=18f1f977e8cd6361eaa1,4vr1DznwcnD-uKM_X7ZhiA) are the following diagrams specific to WebRTC: +- WebRTC Connectivity Diagram outlining the steps in a WebRTC connection and getting to media flow +- Front-end Interface Components explaining each component +- Flow of React/Redux (reacts to events in client to update store) +- WebRTC Front-end Format Experiments + + + + +#### Areas for improvement: -- Currently, our WebRTC only works as the connection initiator. The next step would be the `Add Answer` button which allows Swell to be on the receiver end of the connection. - Currently, our WebRTC end-to-end testing is read-only from the previous implementation. It would be a highly valuable addition to modify the old testing to test the current implementation of webRTC. Integration testing has been started but needs to be finished. Relevant files include - End-to-End:'test/**tests**/subSuites/webRTCTest.js' - Integration: 'test/**tests**/IntegrationTests/webRTCIntegrationTests' +- Front-end/UI improvements – see the excalidraw’s WebRTC Front-end Format Experiments diagram for ideas + Examples: + - Buttons could be named more intuitively and placed in more obvious places for the user to figure out the workflow + - Currently, when testing a video connection, users have to scroll all the way down in the server entry form container to see if their own camera is working. A redesign could help this be more clear + - Ability to rename an individual workspace once added to the left pane (switching between testing four different ‘text’ channels would be easier if you could rename each channel) + - The React Joyride tour could use slight improvement - the last few buttons are in different containers/files and harder for the tour to reach intuitively + - Improve styling for Joyride tour to be consistent with Swell colors and fonts +- The current port is hardcoded - a group could modify this part of the code to test the application on ports other than the default to ensure proper functionality across different ports +- The websocket feature of WebRTC is currently incomplete and commented out (both frontend and backend) and could be implemented +- Currently our WebRTC testing feature is on par with or less robust than other WebRTC testing apps. A group could focus on making it more competitive by adding tracking of certain metrics (including jitter, packet loss, geographically-driven latency, differences across different browsers and devices, emulating losing a wifi connection and switching to mobile data, etc) +- The electron app currently has a large file size not due to the code itself but the use of Electron. We briefly explored transitioning the app to Tauri or Neutralino but held off further research after rescoping. Transitioning to a new cross-platform desktop application framework would have a tangible benefit (even our iteration group members had storage issues while working on project) +- A complete testing overhaul could be a great focus for a new group– many testing fixes were added in the process of developing new features but the quality and consistency of some of the testing may be questionable. Focusing just on testing could result in more purposeful and effective testing + + --- @@ -250,7 +286,25 @@ Finally, if future iterators would like to completely cover the list of API-test --- -## Backlog from Iteration Group v1.18 +## v1.19.0 changelog (WebRTC fixes and updates): + +- WebRTC has officially been moved from “experimental features” to “core features”! + - Text channel improvements: + - Added “Add Answer” button and functionality to be able to complete a channel connection + - Completed functionality for two-way communication via text channel + - Debugged messaging in response window - now the remote user’s incoming messages are visible + - Video channel improvements: + - Enabled audio on video calls + - Added an audio toggle button for turning audio on and off + - Audio-only channel completed + - Finished implementing an audio channel begun by a previous group +- Updated dependencies and versions to un-deprecate the app and make it usable (see “How to download and test the application locally?”) +- Added a React Joyride tour to introduce users to the app and ease their introduction to it +- Added a refresh button to restart testing at any point with a new connection + +--- + +## Backlog from Iteration Groups v1.18 and v1.19 - Fix/Update GitHub Actions for Unit Testing - Create a feature/function/endpoint to delete a mock route @@ -267,7 +321,7 @@ Finally, if future iterators would like to completely cover the list of API-test - Convert WebRTCSessionEntryForm to MUI - Convert WebRTC components to all use material UI as per line 7 of WebRTCComposer.tsx - Combine newRequestSlice.ts and newRequestFieldSlice.ts -- Update Excalidraw if necessary with new features/redux changes +- Update Excalidraw as necessary with new features/redux changes - ErrorBoundary.tsx may not be functional or necessary (Leave for now) --- @@ -366,7 +420,4 @@ Things to consider updating: - Ensure the download links are pointing to the latest version - Any videos/screenshots that have been updated - Any new feature(s) you want to showcase -- Add your names, headshots, and relevant information in the `contributors` section - ---- - +- Add your names, headshots, and relevant information in the `contributors` section \ No newline at end of file diff --git a/package.json b/package.json index f8133ee47..20a01f7a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swell", - "version": "1.18.0", + "version": "1.19.0", "description": "Swell", "main": "main.js", "repository": "https://github.com/open-source-labs/Swell", @@ -47,7 +47,7 @@ "preload.js", "src/server/*" ], - "nodeVersion": "16.15.0", + "nodeVersion": "18.18.0", "nsis": { "createDesktopShortcut": "always" }, @@ -91,15 +91,15 @@ "license": "MIT", "homepage": "http://www.getswell.io", "engines": { - "node": ">=16.15.0", + "node": ">=18.0.0", "npm": ">=7.0.0" }, "dependencies": { "@apollo/client": "^3.5.0", "@apollo/server": "^4.6.0", "@emotion/cache": "^11.11.0", - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "@graphql-tools/schema": "^8.3.10", "@grpc/grpc-js": "^1.6.7", "@grpc/proto-loader": "^0.6.9", @@ -139,13 +139,14 @@ "graphql-tag": "^2.12.6", "graphql-ws": "^5.8.1", "highland": "^2.13.5", - "isolated-vm": "^4.6.0", + "isolated-vm": "^5.0.3", "jest-environment-jsdom": "^29.7.0", "mali": "^0.46.1", "ngrok": "^4.3.1", "node-fetch": "^3.3.0", + "node-gyp": "^11.0.0", "npm": "^8.7.0", - "patch-package": "^6.4.7", + "patch-package": "^6.5.1", "path": "^0.12.7", "prop-types": "^15.8.1", "react": "^18.0.0", @@ -154,6 +155,8 @@ "react-dom": "^18.0.0", "react-dropzone": "^12.1.0", "react-github-btn": "^1.2.2", + "react-icons": "^5.4.0", + "react-joyride": "^2.9.3", "react-redux": "^8.0.1", "react-router-dom": "^6.3.0", "react-split": "^2.0.14", @@ -182,7 +185,7 @@ "@babel/runtime": "^7.17.9", "@mui/icons-material": "^5.6.2", "@mui/lab": "5.0.0-alpha.137", - "@mui/material": "5.14.2", + "@mui/material": "^5.14.2", "@playwright/test": "^1.21.1", "@testing-library/jest-dom": "^6.1.4", "@testing-library/react": "^14.0.0", @@ -569,6 +572,26 @@ { "name": "Nitesh Manem", "url": "https://github.com/NManem" + }, + { + "name": "Kiki Hunt", + "url": "https://github.com/Iloveeverything" + }, + { + "name": "Isaac Mbambo", + "url": "https://github.com/IM236" + }, + { + "name": "Ting Lee", + "url": "https://github.com/tingEng" + }, + { + "name": "Rachel Dean", + "url": "https://github.com/rchldn" + }, + { + "name": "Kadeem Reid", + "url": "https://github.com/Kadeem929" } ] } diff --git a/src/assets/style/WebRtc.css b/src/assets/style/WebRtc.css new file mode 100644 index 000000000..2cd66aaea --- /dev/null +++ b/src/assets/style/WebRtc.css @@ -0,0 +1,94 @@ +.toggle-refresh-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + min-height: 40px; +} + +.refresh-button { + width: 70px; + height: 40px; + border-radius: 30px; + border-style: none; + background-color: #58a4b0; +} + +.refresh-button:hover { + box-shadow: 2px 2px 1px rgba(0, 0, 0, 0.75); +} + +.Audio-Toggle-Container { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 10px; + min-width: 150px; +} + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +.slider-on { + background-color: #58a4b0 !important; +} + +.slider:before { + position: absolute; + content: ''; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +input:focus + .slider { + box-shadow: 0 0 1px #ccc; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +.slider.round { + border-radius: 5px; +} + +.slider.round:before { + border-radius: 50%; +} + +/* .is-3rem-footer.is-clickable.is-margin-top-auto { + display: flex; + justify-content: flex-end; + padding: 0; +} + */ + diff --git a/src/client/components/Versions.tsx b/src/client/components/Versions.tsx index b018d5e85..632d05620 100644 --- a/src/client/components/Versions.tsx +++ b/src/client/components/Versions.tsx @@ -1,6 +1,18 @@ import * as React from 'react'; import { useState } from 'react'; +declare global { + interface Window { + api: { + versions: { + electron: string; + chrome: string; + node: string; + }; + }; + } +} + function Versions(): JSX.Element { const [versions] = useState(window.api.versions); diff --git a/src/client/components/main/WebRTC-composer/WebRTCAudioBox.tsx b/src/client/components/main/WebRTC-composer/WebRTCAudioBox.tsx new file mode 100644 index 000000000..6d5fe2ec5 --- /dev/null +++ b/src/client/components/main/WebRTC-composer/WebRTCAudioBox.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +// possibly not the best practice implementation for audio playback ? +interface Props { + streamType: 'localstream' | 'remotestream'; +} +const WebRTCAudioBox: React.FC = (props: Props) => { + const { streamType } = props; + return ( +
+ + + {streamType} + +
+ ); +}; + +export default WebRTCAudioBox; + diff --git a/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx b/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx index cb45e5b61..ee021e3d0 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx @@ -13,8 +13,12 @@ import NewRequestButton from '../sharedComponents/requestButtons/NewRequestButto // Import MUI components import { Box } from '@mui/material'; import WebRTCVideoBox from './WebRTCVideoBox'; +import WebRTCAudioBox from './WebRTCAudioBox'; import { RootState } from '../../../toolkit-refactor/store'; -import { useAppDispatch, useAppSelector } from '../../../toolkit-refactor/hooks'; +import { + useAppDispatch, + useAppSelector, +} from '../../../toolkit-refactor/hooks'; import { composerFieldsReset } from '../../../toolkit-refactor/slices/newRequestSlice'; import { setWorkspaceActiveTab } from '../../../toolkit-refactor/slices/uiSlice'; import { reqResItemAdded } from '../../../toolkit-refactor/slices/reqResSlice'; @@ -40,7 +44,7 @@ export default function WebRTCComposer() { checkSelected: false, request: newRequestWebRTC, response: { - webRTCMessages: [] + webRTCMessages: [], }, checked: false, minimized: false, @@ -56,20 +60,33 @@ export default function WebRTCComposer() { ) return true; } catch { - return false + return false; } return false; - } + }; const addNewRequest = (): void => { - if (!(checkValidSDP(newRequestWebRTC.webRTCOffer) && checkValidSDP(newRequestWebRTC.webRTCAnswer))){ - return alert('Invalid offer or answer SDP') + console.log('newRequestWebRTCatANR:', newRequestWebRTC); + if ( + !( + checkValidSDP(newRequestWebRTC.webRTCOffer) && + checkValidSDP(newRequestWebRTC.webRTCAnswer) + ) + ) { + return alert('Invalid offer or answer SDP'); } + // let localStream = peerConnection.createDataChannel('textChannel'); + // // localStream.onopen = () => console.log('data channel opened'); + // // localStream.onclose = () => console.log('data channel closed') + // localStream.addEventListener("open", (event) => { + // beginTransmission(localStream); + // }); + const reqRes: ReqRes = composeReqRes(); // addHistory removed because RTCPeerConnection objects cant typically be cloned // historyController.addHistoryToIndexedDb(reqRes); - + console.log('reqRes:', reqRes); dispatch(reqResItemAdded(reqRes)); dispatch(composerFieldsReset()); setShowRTCEntryForms(false); @@ -87,6 +104,7 @@ export default function WebRTCComposer() { style={{ overflowX: 'hidden' }} > + {showRTCEntryForms && ( <> @@ -98,6 +116,12 @@ export default function WebRTCComposer() { )} + + {newRequestWebRTC.webRTCDataChannel === 'Audio' && ( +
+ +
+ )} )} diff --git a/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx b/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx index 0145b8fc9..d67af1f17 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx @@ -1,4 +1,13 @@ import React from 'react'; +import Joyride from 'react-joyride'; +import { useState, useRef, useEffect } from 'react'; +import { MdRefresh } from 'react-icons/md'; +import { Placement } from 'react-joyride'; + + +// import '/Users/katharinehunt/Swell/src/assets/style/WebRtcEntry.css'; +import '../../../../assets/style/WebRtc.css'; + // import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; // import CodeMirror from '@uiw/react-codemirror'; // import { EditorView } from '@codemirror/view'; @@ -6,12 +15,19 @@ import React from 'react'; // import { vscodeDark } from '@uiw/codemirror-theme-vscode'; // import Select from '@mui/material/Select'; // import MenuItem from '@mui/material/MenuItem'; -import { RequestWebRTC } from '../../../../types'; +import { ReqRes, RequestWebRTC } from '../../../../types'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -import { useAppDispatch, useAppSelector } from '../../../toolkit-refactor/hooks'; -import { newRequestWebRTCSet } from '../../../toolkit-refactor/slices/newRequestSlice'; +import { + useAppDispatch, + useAppSelector, +} from '../../../toolkit-refactor/hooks'; +import { + resetWebRTCconnection, + newRequestWebRTCSet, +} from '../../../toolkit-refactor/slices/newRequestSlice'; import webrtcPeerController from '../../../controllers/webrtcPeerController'; import { RootState } from '../../../toolkit-refactor/store'; +import { compose } from 'redux'; // const jBeautify = require('js-beautify').js; @@ -23,132 +39,321 @@ interface Props { } const WebRTCServerEntryForm: React.FC = (props: Props) => { + const [isToggled, setIsToggled] = useState(false); + + const [run, setRun] = useState(true); const dispatch = useAppDispatch(); + const newRequestWebRTC: RequestWebRTC = useAppSelector( (store: RootState) => store.newRequest.newRequestWebRTC ); + const currentReqRes = useAppSelector( + (store: RootState) => store.reqRes.currentResponse + ) as ReqRes; + + useEffect(() => { + setIsToggled(newRequestWebRTC.enableAudio as boolean); + }, [newRequestWebRTC.enableAudio]); + + const handleToggleChange = () => { + // toggles state os is toggled + const newToggleState = !isToggled; + setIsToggled(newToggleState); + // console.log( + // 'Dispatching newRequestWebRTCSet with enableAudio for handleToggleChange:', + // newToggleState + // ); + + dispatch( + //sends action to redux store + // when dispatched redux store updates state based on action + newRequestWebRTCSet({ + //creates action takes object as argument + ...newRequestWebRTC, + enableAudio: newToggleState, //updates Enableaudio property in newRequestWebRTC to match toggle state + }) //enable audio updated every time toggle state changes + ); + + // console.log( + // 'enableAudio Redux state just after dispatch:', + // newRequestWebRTC.enableAudio + // ); + webrtcPeerController.createPeerConnection( + { + ...newRequestWebRTC, + enableAudio: newToggleState, + }, + currentReqRes + ); + }; + const steps = [ + { + target: '.get-offer-button', + content: 'Caller: Generate an offer by clicking “Get Offer”.', + placement: 'bottom' as Placement, + }, + { + target: '.copy-offer-button', // Target the "Copy" button in the Offer code box + content: + 'Caller: Copy to clipboard, paste and send to recipient (email recommended).', + placement: 'bottom' as Placement, + }, + { + target: '.offer-paste-button', + content: 'Recipient: Copy the offer received and paste into the top box', + placement: 'bottom' as Placement, + }, + { + target: '.get-answer-btn', + content: "Recipient: Click 'Get Answer' and copy it.", + placement: 'bottom' as Placement, + }, + { + target: '.answer-paste-button', + content: 'Caller: Paste the answer here.', + }, + { + target: '.add-answer-btn', + content: + "Caller: Click 'Add Answer' to establish the connection. Then click 'Add to Workspace' button below", + }, + { + target: '.add-to-workspace-btn', + content: "Caller: Click 'Add to Workspace'.", + }, + ]; + // Use useEffect to start the joyride after the component mounts + useEffect(() => { + // Delay the start of Joyride to ensure everything is rendered + const timer = setTimeout(() => setRun(true), 500); + + return () => clearTimeout(timer); // Clear the timer on cleanup + }, []); + + const hasResetRef = useRef(false); + + const handleResetWebRTCconnection = () => { + dispatch(resetWebRTCconnection()); + console.log('WebRTC connection reset to initial state:'); + console.log('newRequestWebRTCFromConnect:', { + newRequestWebRTC: newRequestWebRTC, // This will be the empty reset state + }); + hasResetRef.current = true; + }; + + useEffect(() => { + // so we only trigger a new peer connection here for the reset if the offer has been cleared specifically via our reset function + if (hasResetRef.current && newRequestWebRTC.webRTCOffer === '') { + console.log('Creating a new Peer Connection with reset state'); + webrtcPeerController.createPeerConnection( + newRequestWebRTC, + currentReqRes + ); + hasResetRef.current = false; + } + }, [newRequestWebRTC, currentReqRes]); return (
+
+
+ {newRequestWebRTC.webRTCDataChannel === 'Video' && ( + <> + + Audio + + + + )} +
+ +
+ +
+
{ + console.log('value before dispatch:', value); dispatch( newRequestWebRTCSet({ ...newRequestWebRTC, webRTCOffer: value }) ); + // console.log( + // 'value after dispatch, Im assuming it is the same:', + // value + // ); }} placeholder={'Click "Get Offer" or paste in Offer SDP'} readOnly={true} /> - - - -
- {/* Code box for Answer */} -
- { - dispatch( - newRequestWebRTCSet({ ...newRequestWebRTC, webRTCAnswer: value }) - ); - }} - placeholder={'Answer here'} - readOnly={true} - /> - - + + + + +
+ {/* Code box for Answer */} +
+ { dispatch( newRequestWebRTCSet({ ...newRequestWebRTC, - webRTCAnswer: text, + webRTCAnswer: value, }) - ) - ); - }} - > - Paste - - {/* ANSWER BUTTON IS WORK-IN-PROGRESS */} - {/* */} - {/* {warningMessage ?
{warningMessage.body}
: null} */} + ); + console.log( + 'newRequestWebRTC (though may not be updated bc async):', + newRequestWebRTC + ); + }} + placeholder={'Answer here'} + readOnly={true} + /> + + + + + {/* {warningMessage ?
{warningMessage.body}
: null} */} +
); diff --git a/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx b/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx index d9a8c29b1..6719900ed 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; -import { NewRequestWebRTCSet, RequestWebRTC } from '../../../../types'; +import { NewRequestWebRTCSet, RequestWebRTC, ReqRes } from '../../../../types'; import webrtcPeerController from '../../../controllers/webrtcPeerController'; - import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; -import { useAppDispatch, useAppSelector } from '../../../toolkit-refactor/hooks'; +import { + useAppDispatch, + useAppSelector, +} from '../../../toolkit-refactor/hooks'; import { newRequestWebRTCSet } from '../../../toolkit-refactor/slices/newRequestSlice'; import { RootState } from '../../../toolkit-refactor/store'; @@ -16,8 +18,13 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { const newRequestWebRTC: RequestWebRTC = useAppSelector( (store: RootState) => store.newRequest.newRequestWebRTC ); + const currentReqRes = useAppSelector( + (store: RootState) => store.reqRes.currentResponse + ) as ReqRes; - const isDark = useAppSelector((store: { ui: { isDark: boolean }}) => store.ui.isDark); + const isDark = useAppSelector( + (store: { ui: { isDark: boolean } }) => store.ui.isDark + ); const { setShowRTCEntryForms } = props; const [entryTypeDropdownIsActive, setEntryTypeDropdownIsActive] = @@ -25,6 +32,9 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { const [dataTypeDropdownIsActive, setDataTypeDropdownIsActive] = useState(false); + // may have to have a connect button for peer 1 and a different one for peer 2 + // this is because peer 2 does not need to create a data channel, rather they receive one from peer 1 + return (
= (props: Props) => { id="rest-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => - setEntryTypeDropdownIsActive(!entryTypeDropdownIsActive) - } + // onClick={() => + // setEntryTypeDropdownIsActive(!entryTypeDropdownIsActive) + // } > {newRequestWebRTC.webRTCEntryMode} - + {/* - + */}
@@ -60,10 +70,12 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { {newRequestWebRTC.webRTCEntryMode !== 'Manual' && ( { - dispatch(newRequestWebRTCSet({ - ...newRequestWebRTC, - webRTCEntryMode: 'Manual', - })); + dispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCEntryMode: 'Manual', + }) + ); setEntryTypeDropdownIsActive(false); }} className="dropdown-item" @@ -71,27 +83,29 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { Manual )} - + {/* //use this if you want to enable websocket feature as signaling server {newRequestWebRTC.webRTCEntryMode !== 'WS' && ( { - dispatch(newRequestWebRTCSet({ - ...newRequestWebRTC, - webRTCEntryMode: 'WS', - })); + dispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCEntryMode: 'WS', + }) + ); setEntryTypeDropdownIsActive(false); }} className="dropdown-item" > WS - )} + )} */}
= (props: Props) => { className="ml-1 is-rest button no-border-please" onClick={() => { setShowRTCEntryForms(true); - webrtcPeerController.createPeerConnection(newRequestWebRTC); + console.log('newRequestWebRTCFromConnect:', { + newRequestWebRTC: newRequestWebRTC, + }); + webrtcPeerController.createPeerConnection( + newRequestWebRTC, + currentReqRes + ); }} > Connect @@ -140,49 +160,64 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { ); }; - +// update export default WebRTCTextContainer; diff --git a/src/client/components/main/sharedComponents/TextCodeArea.tsx b/src/client/components/main/sharedComponents/TextCodeArea.tsx index ce7eeac26..5e2258d52 100644 --- a/src/client/components/main/sharedComponents/TextCodeArea.tsx +++ b/src/client/components/main/sharedComponents/TextCodeArea.tsx @@ -13,6 +13,7 @@ interface TextCodeAreaProps { mode: string; onChange: (value: string, viewUpdate: ViewUpdate) => void; height?: string; + width?: string; placeholder?: string; readOnly?: boolean; } @@ -38,6 +39,7 @@ export default function TextCodeArea({ height = '200px', placeholder = 'Enter body here', readOnly = false, + width, }: TextCodeAreaProps) { const lang: string = mode.substring(mode.indexOf('/') + 1); // Grab language mode based on value passed in return ( @@ -45,6 +47,7 @@ export default function TextCodeArea({ { className="is-flex-basis-0 is-flex-grow-1 button is-primary-100 is-size-7 border-curve" id={`send-button-${index}`} onClick={() => { + console.log('content:',content); if (content.request.network === 'webrtc') { dispatch(setResponsePaneActiveTab('webrtc')); dispatch(responseDataSaved(content)); - webrtcPeerController.addAnswer(content, currentResponse); + console.log('currentResponse:',currentResponse); + webrtcPeerController.dataStream(content, currentResponse); setWebRTCSend(true); // connectionController.setReqResConnectionToClosed(content.id); } else if (content.graphQL && request.method === 'SUBSCRIPTION') { diff --git a/src/client/controllers/graphQLController.ts b/src/client/controllers/graphQLController.ts index c05d5b184..736e13b81 100644 --- a/src/client/controllers/graphQLController.ts +++ b/src/client/controllers/graphQLController.ts @@ -66,14 +66,11 @@ const graphQLController: GqlController = { .then((response) => { if (response.error) { this.handleError(response.reqResObj.error, response.reqResObj); - } - - else this.handleResponse(response.data, response.reqResObj); + } else this.handleResponse(response.data, response.reqResObj); }) .catch((err) => console.log('error in sendGqlToMain', err)); }, - openGraphQLConnectionAndRunCollection(reqResArray: ReqRes[]): void { // initialize response data let index = 0; @@ -105,7 +102,7 @@ const graphQLController: GqlController = { }); const runSingleGraphQLRequest = (reqResObj: ReqRes) => { - reqResObj.response.headers = {}; + reqResObj.response.headers = {}; reqResObj.response.events = []; reqResObj.response.cookies = []; reqResObj.connection = 'open'; @@ -297,7 +294,7 @@ const graphQLController: GqlController = { }; api.send('introspect', JSON.stringify(introspectionObject)); api.receive('introspect-reply', (data: IntrospectionQuery) => { - // console.log(data); + // console.log(data); if (data !== 'Error: Please enter a valid GraphQL API URI') { // formatted for Codemirror hint and lint const clientSchema = buildClientSchema(data); diff --git a/src/client/controllers/webrtcPeerController.ts b/src/client/controllers/webrtcPeerController.ts index 626529729..f2fae20dc 100644 --- a/src/client/controllers/webrtcPeerController.ts +++ b/src/client/controllers/webrtcPeerController.ts @@ -2,6 +2,7 @@ import store, { appDispatch } from '../toolkit-refactor/store'; import { newRequestWebRTCSet, newRequestWebRTCOfferSet, + newRequestWebRTCAnswerSet, } from '../toolkit-refactor/slices/newRequestSlice'; import { ReqRes, @@ -11,26 +12,58 @@ import { ResponseWebRTCText, } from '../../types'; import { responseDataSaved } from '../toolkit-refactor/slices/reqResSlice'; +import { send } from 'process'; + const webrtcPeerController = { createPeerConnection: async ( - newRequestWebRTC: RequestWebRTC - ): Promise => { + newRequestWebRTC: RequestWebRTC, + currentReqRes: ReqRes + ) => { + // const enableAudio = + // store.getState().newRequest.newRequestWebRTC.enableAudio ?? false; + const enableAudio = newRequestWebRTC.enableAudio ?? false; //if null set to false + let servers = { iceServers: [ { - urls: [ - 'stun:stun1.1.google.com:19302', - 'stun:stun2.1.google.com:19302', - ], + urls: ['stun:stun.l.google.com:19302', 'stun:stun.l.google.com:5349'], }, ], }; let peerConnection = new RTCPeerConnection(servers); + // ? if you want to attempt to build a websocket with a live connection through ngrok, + // ? give it a shot - we attempted below, this may be helpful, probably isn't (-Isaac M) + + // let wsIp = newRequestWebRTC.webRTCWebsocketServer; + // const socket = io('http://localhost:3000'); + + // socket.on('connect', async () => { + // // try { + // // const url = await ngrok.connect({ + // // proto: 'http', + // // addr: 3000, + // // }); + // // console.log(`ngrok tunnel opened at: ${url}`); + // // // client.emit('ngrokUrl', url); + // // } catch (err) { + // // console.error('Failed to create ngrok tunnel:', err); + // // } + // console.log('Connected to server'); + // }); + + // socket.on('disconnect', () => { + // console.log('Disconnected from server'); + // }); + + // socket.on('message', (message: string) => { + // console.log('Message:', message); + // }); if (newRequestWebRTC.webRTCDataChannel === 'Video') { let localStream = await navigator.mediaDevices.getUserMedia({ - video: true, - audio: false, + // request access to user camera + video: true, // request access to video + audio: enableAudio, //if enable audio is true request access to audio //not if false }); if (document.getElementById('localstream')) { @@ -55,61 +88,194 @@ const webrtcPeerController = { webRTCpeerConnection: peerConnection, webRTCLocalStream: localStream, webRTCRemoteStream: remoteStream, + enableAudio, }) ); - peerConnection.onicecandidate = async ( event: RTCPeerConnectionIceEvent ): Promise => { - if (event.candidate) { + if ( + event.candidate && + peerConnection.localDescription!.type === 'offer' + ) { appDispatch( newRequestWebRTCOfferSet( JSON.stringify(peerConnection.localDescription) ) ); + } else if ( + event.candidate && + peerConnection.localDescription!.type === 'answer' + ) { + appDispatch( + newRequestWebRTCAnswerSet( + JSON.stringify(peerConnection.localDescription) + ) + ); } }; - } else if (newRequestWebRTC.webRTCDataChannel === 'Text') { - let localStream = peerConnection.createDataChannel('textChannel'); - localStream.onopen = () => console.log('data channel opened'); - localStream.onclose = () => console.log('data channel closed') + } else if (newRequestWebRTC.webRTCDataChannel === 'Audio') { + let localStream = await navigator.mediaDevices.getUserMedia({ + // request access to user camera + video: false, // request access to video + audio: true, //if enable audio is true request access to audio //not if false + }); // from + + if (document.getElementById('localstream')) { + (document.getElementById('localstream')).srcObject = + localStream; + } + + localStream.getTracks().forEach((track) => { + // iterates over tracks in local stream + peerConnection.addTrack(track, localStream); + }); // adds them to the peer connection using add track + + let remoteStream = new MediaStream(); //initialize new media stream object + peerConnection.ontrack = async (event) => { + // adds them to the peer connection using add track + event.streams[0].getTracks().forEach((track) => { + // sets up an event handler for on track event of peer connection object + // returns array of tracks + remoteStream.addTrack(track); + }); + }; appDispatch( + // update redux state newRequestWebRTCSet({ - ...newRequestWebRTC, + //new state object is created to update redux object + ...newRequestWebRTC, // copy properties from existing object webRTCpeerConnection: peerConnection, webRTCLocalStream: localStream, + webRTCRemoteStream: remoteStream, // remote stream overwritten }) ); - peerConnection.onicecandidate = async ( event: RTCPeerConnectionIceEvent ): Promise => { - if (event.candidate) { + if ( + event.candidate && + peerConnection.localDescription!.type === 'offer' + ) { appDispatch( newRequestWebRTCOfferSet( JSON.stringify(peerConnection.localDescription) ) ); + } else if ( + event.candidate && + peerConnection.localDescription!.type === 'answer' + ) { + appDispatch( + newRequestWebRTCAnswerSet( + JSON.stringify(peerConnection.localDescription) + ) + ); } }; + } else if (newRequestWebRTC.webRTCDataChannel === 'Text') { + // const { request, response } = currentReqRes as { + // request: RequestWebRTCText; + // response: ResponseWebRTCText; + // }; + + let localStream = peerConnection.createDataChannel('textChannel'); + localStream.onopen = () => console.log('data channel opened!!!'); + localStream.onclose = () => console.log('data channel closed :('); + + peerConnection.ondatachannel = (event) => { + const receiveChannel = event.channel; + // receiveChannel.onmessage = (event) => { + // console.log('message received:', event.data); + + // }; + receiveChannel.onmessage = (event: MessageEvent) => { + let newString = event.data.slice(1, -1); + let messageObject = { + data: newString, + timeReceived: Date.now(), + }; + + let state = store.getState(); + if (state.reqRes.currentResponse.response) { + let newWebRTCMessages = (( + state.reqRes.currentResponse.response + )).webRTCMessages.concat(messageObject); + let request = state.reqRes.currentResponse.request; + appDispatch( + responseDataSaved({ + ...currentReqRes, + request, + response: { + webRTCMessages: newWebRTCMessages, + }, + }) + ); + } + }; + }; + appDispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCpeerConnection: peerConnection, + webRTCLocalStream: localStream, + }) + ); } + peerConnection.onicecandidate = async ( + event: RTCPeerConnectionIceEvent + ) => { + // sets up event handler for onice canditates + if ( + event.candidate && + peerConnection.localDescription!.type === 'offer' + ) { + // ? more websocket hints below (-Isaac) + // ? // socket.emit('offer', JSON.stringify(peerConnection.localDescription)); + // checks for canidate and if event type is offer + appDispatch( + newRequestWebRTCOfferSet( + JSON.stringify(peerConnection.localDescription) + ) + ); // dispatches action to set a new WebRTC offer request + } else if ( + event.candidate && + peerConnection.localDescription!.type === 'answer' + ) { + // checks for canditate with description type answer + appDispatch( + newRequestWebRTCAnswerSet( + JSON.stringify(peerConnection.localDescription) + ) + ); //dispatches action to create answer + } + }; }, - + // what in create offer triggers the ice candidate to be sent? createOffer: async (newRequestWebRTC: RequestWebRTC): Promise => { //grab the peer connection off the state to manipulate further + console.log('checking peer connection inside createOffer'); + console.log( + 'webRTCpeerConnection exists:', + !!newRequestWebRTC.webRTCpeerConnection + ); + let { webRTCpeerConnection } = newRequestWebRTC; + if (!webRTCpeerConnection) return; + console.log('webRTCPeerConnect:', webRTCpeerConnection); let offer = await webRTCpeerConnection!.createOffer(); - await webRTCpeerConnection!.setLocalDescription(offer); + console.log('offer:', offer); + await webRTCpeerConnection!.setLocalDescription(offer); //what is this line doing that is not already done? appDispatch( newRequestWebRTCSet({ + // newRequestWebRTCSet mutates the newRequestWebRTC state to have the offer ...newRequestWebRTC, webRTCOffer: JSON.stringify(offer), }) ); }, - // work-in-progress createAnswer: async (newRequestWebRTC: RequestWebRTC): Promise => { let { webRTCpeerConnection, webRTCOffer } = newRequestWebRTC; @@ -119,6 +285,7 @@ const webrtcPeerController = { await webRTCpeerConnection.setRemoteDescription(offer); let answer = await webRTCpeerConnection.createAnswer(); + console.log('answer:', answer); await webRTCpeerConnection.setLocalDescription(answer); appDispatch( @@ -127,18 +294,31 @@ const webrtcPeerController = { webRTCAnswer: JSON.stringify(answer), }) ); + console.log('newRequestWebRTCCheckAfterAnswer:', newRequestWebRTC); }, - addAnswer: async (reqRes: ReqRes): Promise => { + addAnswer: async (newRequestWebRTC: RequestWebRTC): Promise => { + let { webRTCpeerConnection } = newRequestWebRTC; + let answer = JSON.parse(newRequestWebRTC.webRTCAnswer); + await webRTCpeerConnection!.setRemoteDescription(answer); + }, + + sendMessages: async (reqRes: ReqRes, messages: string): Promise => { + let { request } = reqRes as { request: RequestWebRTCText }; + console.log('im here too'); + console.log('request from mesaages :', request); + + (request).webRTCLocalStream!.send( + JSON.stringify({ data: messages }) + ); + }, + + dataStream: async (reqRes: ReqRes): Promise => { let { request, response } = reqRes as { request: RequestWebRTC; response: ResponseWebRTC; }; - request.webRTCpeerConnection!.setRemoteDescription( - JSON.parse(request.webRTCAnswer) - ); - if (request.webRTCDataChannel === 'Video') { request.webRTCpeerConnection!.ontrack = async (event: RTCTrackEvent) => { event.streams[0].getTracks().forEach((track: MediaStreamTrack) => { @@ -174,11 +354,10 @@ const webrtcPeerController = { let state = store.getState(); if (state.reqRes.currentResponse.response) { - let newWebRTCMessages = - (state.reqRes.currentResponse.response).webRTCMessages.concat( - messageObject - ); - let request = state.reqRes.currentResponse.request + let newWebRTCMessages = (( + state.reqRes.currentResponse.response + )).webRTCMessages.concat(messageObject); + let request = state.reqRes.currentResponse.request; appDispatch( responseDataSaved({ ...reqRes, diff --git a/src/client/toolkit-refactor/slices/newRequestSlice.ts b/src/client/toolkit-refactor/slices/newRequestSlice.ts index 09f8d5515..308c2e062 100644 --- a/src/client/toolkit-refactor/slices/newRequestSlice.ts +++ b/src/client/toolkit-refactor/slices/newRequestSlice.ts @@ -30,7 +30,6 @@ type NewRequestStore = { newRequestBody: NewRequestBody; newRequestSSE: NewRequestSSE; newRequestWebRTC: RequestWebRTC; - }; const initialState: NewRequestStore = { @@ -47,7 +46,7 @@ const initialState: NewRequestStore = { bodyIsNew: false, }, newRequestStreams: { - streamsArr: [], + streamsArr: [], count: 0, streamContent: [], selectedPackage: null, @@ -79,7 +78,7 @@ const initialState: NewRequestStore = { webRTCLocalStream: null, webRTCRemoteStream: null, webRTCMessages: [], - } + }, }; const newRequestSlice = createSlice({ @@ -101,11 +100,18 @@ const newRequestSlice = createSlice({ newRequestWebRTCSet: (state, action: PayloadAction) => { state.newRequestWebRTC = action.payload; + // console.log('newRequestWebRTCCheckAfterAnswerInReducer:', state.newRequestWebRTC.webRTCAnswer); }, newRequestWebRTCOfferSet: (state, action: PayloadAction) => { state.newRequestWebRTC.webRTCOffer = action.payload; }, + newRequestWebRTCAnswerSet: (state, action: PayloadAction) => { + state.newRequestWebRTC.webRTCAnswer = action.payload; + }, + resetWebRTCconnection: () => { + return initialState; + }, //Before toolkit conversion was SET_NEW_REQUEST_STREAMS or setNewRequestStreams newRequestStreamsSet: (state, action: PayloadAction) => { @@ -188,6 +194,8 @@ export const { newRequestContentByProtocol, newRequestWebRTCSet, newRequestWebRTCOfferSet, + newRequestWebRTCAnswerSet, + resetWebRTCconnection, } = newRequestSlice.actions; export default newRequestSlice.reducer; diff --git a/src/server/server.js b/src/server/server.js index fa07fd840..e7b03a62c 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -6,7 +6,7 @@ const cookieParser = require('cookie-parser'); const crypto = require('crypto'); dotenv.config(); -const port = 3000; +const port = 3001; const app = express(); const cors = require('cors'); app.use(express.urlencoded({ extended: true })); diff --git a/src/types.ts b/src/types.ts index a01c38eaa..adfdcad9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -150,7 +150,7 @@ export interface Message { data: string; } -export interface NewRequestBody { +export interface NewRequestBody { bodyContent: string; bodyVariables: string; bodyType: string; @@ -323,10 +323,16 @@ export type ResponseWebRTC = ResponseWebRTCText; export interface ResponseWebRTCText { webRTCMessages: WebMessages[]; -} - -export type RequestWebRTC = RequestWebRTCVideo | RequestWebRTCText; + events?: any[]; + headers?: Record; + cookies?: any[]; +} +//Defines web rtc request type that can be audio video or text +//enable audio is a shared but optional property +export type RequestWebRTC = { + enableAudio?: boolean; +} & (RequestWebRTCVideo | RequestWebRTCText | RequestWebRTCAudio); export interface RequestWebRTCVideo { network: 'webrtc'; webRTCEntryMode: 'Manual' | 'WS'; @@ -337,6 +343,20 @@ export interface RequestWebRTCVideo { webRTCpeerConnection: RTCPeerConnection | null; webRTCLocalStream: MediaStream | null; webRTCRemoteStream: MediaStream | null; + enableAudio?: boolean; +} + +export interface RequestWebRTCAudio { + network: 'webrtc'; + webRTCEntryMode: 'Manual' | 'WS'; + webRTCDataChannel: 'Audio'; + webRTCWebsocketServer: string; + webRTCOffer: string; + webRTCAnswer: string; + webRTCpeerConnection: RTCPeerConnection | null; + webRTCLocalStream: MediaStream | null; + webRTCRemoteStream: MediaStream | null; + enableAudio?: boolean; } export interface RequestWebRTCText { diff --git a/webpack.config.js b/webpack.config.js index c31f08d20..a336f60ca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -129,8 +129,8 @@ module.exports = { 'base-uri': ["'self'"], 'default-src': [ "'self'", - 'http://localhost:3000', - 'ws://localhost:3000', + 'http://localhost:3001', + 'ws://localhost:3001', 'https://api.github.com', "'unsafe-inline'", "'unsafe-eval'", diff --git a/webpack.development.js b/webpack.development.js index 1b734d578..cf87d7e82 100644 --- a/webpack.development.js +++ b/webpack.development.js @@ -13,16 +13,16 @@ module.exports = merge(base, { compress: true, proxy: { '/webhookServer': { - target: 'http://localhost:3000', + target: 'http://localhost:3001', }, '/webhook': { - target: 'http://localhost:3000', + target: 'http://localhost:3001', }, '/api': { - target: 'http://localhost:3000', + target: 'http://localhost:3001', /** * @todo Change secure option to true, and refactor code to account for - * change + * change // https://github.com/electron/electron/issues/19775 ??? maybe this is the solution */ secure: false, }, @@ -31,6 +31,7 @@ module.exports = merge(base, { if (!devServer) { throw new Error('webpack-dev-server is not defined'); } + // console.log('Setting up middlewares...:', middlewares); middlewares.unshift({ // unshift does not work, ends in infinite calls to this function name: 'run-in-electron',