├── .env ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── build ├── asset-manifest.json ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── favicon.ico ├── index.html ├── manifest.json ├── precache-manifest.3002c5ce158447f31a94202dfc4320a4.js ├── robots.txt ├── service-worker.js └── static │ ├── css │ ├── 2.7fb08d59.chunk.css │ ├── 2.7fb08d59.chunk.css.map │ ├── main.c307118f.chunk.css │ └── main.c307118f.chunk.css.map │ └── js │ ├── 2.4e742ba6.chunk.js │ ├── 2.4e742ba6.chunk.js.LICENSE.txt │ ├── 2.4e742ba6.chunk.js.map │ ├── main.c8bd8dfa.chunk.js │ ├── main.c8bd8dfa.chunk.js.map │ ├── runtime-main.07971bfb.js │ └── runtime-main.07971bfb.js.map ├── config-overrides.js ├── docker-compose.yml ├── package-lock.json ├── package.json ├── public ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt ├── screenshots └── screen_1.png └── src ├── App.css ├── App.jsx ├── App.test.js ├── Helpers.js ├── Routes.jsx ├── Store.js ├── components ├── LogItem.jsx ├── PayloadPopup.jsx └── WsClient.jsx ├── index.js ├── layouts └── AppLayout.jsx ├── locale ├── i18n.js └── translations │ └── en.js ├── logo.png ├── serviceWorker.js └── setupTests.js /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_APP_NAME="Socktest" 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_APP_NAME="WebSocket Tester React" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | # /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .idea 26 | .vscode 27 | .package-lock.json 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:17-alpine3.12 2 | 3 | EXPOSE 3000 4 | 5 | RUN mkdir -p /socktest 6 | 7 | WORKDIR /socktest 8 | 9 | RUN npm install -g serve 10 | 11 | CMD [ "serve", "-s", "-C" ] 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 rahulhaque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socktest 🔌 WebSocket Tester React 2 | 3 | **Socktest** is a simple websocket client application built with React for testing and debugging websocket server. It can connect to a websocket server and send payloads and receive responses from the server along with tracking connection activity log. 4 | 5 | Demo - [Socktest](https://rahulhaque.github.io/websocket-tester-react/build) 6 | 7 | ## Features 8 | 9 | - Connect to any websocket. 10 | - Send and receive payloads and responses. 11 | - Payload is saved on successful send. 12 | - Multiple payloads storage support. 13 | - Websocket protocol support. 14 | - Realtime persistent connection activity log. 15 | - Auto reconnect option. 16 | - Clear log view. 17 | - Responsive UI. 18 | - JSON highlighter. 19 | - Progressive Web App (Works both online and offline). 20 | 21 | ## Shortcuts 22 | 23 | - Protocol set on `enter` press. 24 | 25 | ## Usage 26 | 27 | - Clone or download the repo. 28 | - `cd` into directory and run `npm i` to install the dependencies. 29 | - Run `npm start` to launch the app. 30 | - Go to `http://localhost:3000` 31 | 32 | ## Docker 33 | 34 | - Run `docker-compose up -d` to start. 35 | - Go to `http://localhost:3000` 36 | - Run `docker-compose down` to stop. 37 | 38 | ## Screenshots 39 | 40 | 41 | 42 | ## More Info 43 | 44 | This is a basic approach towards making a websocket tester app which can be used to test the logic of any websocket server. Often we need to implement a websocket server for ourselves. However, testing process of the inner logic of the websocket quickly becomes tiresome. This app aims to lessen that effort. 45 | 46 | Spare a ⭐ to keep me motivated. 😃 47 | 48 | ## License 49 | 50 | This software is licensed under the MIT License (MIT). You are free to use and modify the code. A simple mention or reference would be highly appreciated. 51 | -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.c307118f.chunk.css", 4 | "main.js": "./static/js/main.c8bd8dfa.chunk.js", 5 | "main.js.map": "./static/js/main.c8bd8dfa.chunk.js.map", 6 | "runtime-main.js": "./static/js/runtime-main.07971bfb.js", 7 | "runtime-main.js.map": "./static/js/runtime-main.07971bfb.js.map", 8 | "static/css/2.7fb08d59.chunk.css": "./static/css/2.7fb08d59.chunk.css", 9 | "static/js/2.4e742ba6.chunk.js": "./static/js/2.4e742ba6.chunk.js", 10 | "static/js/2.4e742ba6.chunk.js.map": "./static/js/2.4e742ba6.chunk.js.map", 11 | "index.html": "./index.html", 12 | "precache-manifest.3002c5ce158447f31a94202dfc4320a4.js": "./precache-manifest.3002c5ce158447f31a94202dfc4320a4.js", 13 | "service-worker.js": "./service-worker.js", 14 | "static/css/2.7fb08d59.chunk.css.map": "./static/css/2.7fb08d59.chunk.css.map", 15 | "static/css/main.c307118f.chunk.css.map": "./static/css/main.c307118f.chunk.css.map", 16 | "static/js/2.4e742ba6.chunk.js.LICENSE.txt": "./static/js/2.4e742ba6.chunk.js.LICENSE.txt" 17 | }, 18 | "entrypoints": [ 19 | "static/js/runtime-main.07971bfb.js", 20 | "static/css/2.7fb08d59.chunk.css", 21 | "static/js/2.4e742ba6.chunk.js", 22 | "static/css/main.c307118f.chunk.css", 23 | "static/js/main.c8bd8dfa.chunk.js" 24 | ] 25 | } -------------------------------------------------------------------------------- /build/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /build/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /build/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /build/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/favicon-16x16.png -------------------------------------------------------------------------------- /build/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/favicon-32x32.png -------------------------------------------------------------------------------- /build/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/assets/favicon.ico -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/build/favicon.ico -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | Socktest
-------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Socktest", 3 | "name": "Socktest", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "./assets/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "./assets/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#1a1d24", 24 | "background_color": "#1a1d24" 25 | } 26 | -------------------------------------------------------------------------------- /build/precache-manifest.3002c5ce158447f31a94202dfc4320a4.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "fab96fe22cac81f69da95c60df936bab", 4 | "url": "./index.html" 5 | }, 6 | { 7 | "revision": "2ab556e0a4107ead237d", 8 | "url": "./static/css/2.7fb08d59.chunk.css" 9 | }, 10 | { 11 | "revision": "4f85606938675f87da22", 12 | "url": "./static/css/main.c307118f.chunk.css" 13 | }, 14 | { 15 | "revision": "2ab556e0a4107ead237d", 16 | "url": "./static/js/2.4e742ba6.chunk.js" 17 | }, 18 | { 19 | "revision": "5ac48c47bb3912b14c2d8de4f56d5ae8", 20 | "url": "./static/js/2.4e742ba6.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "4f85606938675f87da22", 24 | "url": "./static/js/main.c8bd8dfa.chunk.js" 25 | }, 26 | { 27 | "revision": "f98c523b50595ac929b0", 28 | "url": "./static/js/runtime-main.07971bfb.js" 29 | } 30 | ]); -------------------------------------------------------------------------------- /build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "./precache-manifest.3002c5ce158447f31a94202dfc4320a4.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("./index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^\/?]+\.[^\/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /build/static/css/main.c307118f.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0}.navbar-brand{padding:18px 20px;display:inline-block}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}textarea.rs-input{min-width:200px}.rs-notification{top:12px!important;right:12px;bottom:12px;max-width:294px}.rs-notification-item-content{padding:15px}.show-grid [class*=rs-col-]{padding-top:10px;margin-top:10px} 2 | /*# sourceMappingURL=main.c307118f.chunk.css.map */ -------------------------------------------------------------------------------- /build/static/css/main.c307118f.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,SACF,CAEA,cACE,iBAAkB,CAClB,oBACF,CAEA,wCACE,oBACF,CAGA,kBACE,eACF,CAEA,iBACE,kBAAoB,CACpB,UAAW,CACX,WAAY,CACZ,eACF,CAEA,8BACE,YACF,CAEA,4BACI,gBAAiB,CACjB,eACJ","file":"main.c307118f.chunk.css","sourcesContent":["body {\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.navbar-brand {\r\n padding: 18px 20px;\r\n display: inline-block;\r\n}\r\n\r\n.navbar-brand:hover, .navbar-brand:focus {\r\n text-decoration: none;\r\n}\r\n\r\n/* For iPhone 5/SE */\r\ntextarea.rs-input {\r\n min-width: 200px;\r\n}\r\n\r\n.rs-notification {\r\n top: 12px !important;\r\n right: 12px;\r\n bottom: 12px;\r\n max-width: 294px;\r\n}\r\n\r\n.rs-notification-item-content {\r\n padding: 15px;\r\n}\r\n\r\n.show-grid [class*=rs-col-] {\r\n padding-top: 10px;\r\n margin-top: 10px;\r\n}\r\n"]} -------------------------------------------------------------------------------- /build/static/js/2.4e742ba6.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /** @license React v0.19.1 14 | * scheduler.production.min.js 15 | * 16 | * Copyright (c) Facebook, Inc. and its affiliates. 17 | * 18 | * This source code is licensed under the MIT license found in the 19 | * LICENSE file in the root directory of this source tree. 20 | */ 21 | 22 | /** @license React v16.13.1 23 | * react-dom.production.min.js 24 | * 25 | * Copyright (c) Facebook, Inc. and its affiliates. 26 | * 27 | * This source code is licensed under the MIT license found in the 28 | * LICENSE file in the root directory of this source tree. 29 | */ 30 | 31 | /** @license React v16.13.1 32 | * react.production.min.js 33 | * 34 | * Copyright (c) Facebook, Inc. and its affiliates. 35 | * 36 | * This source code is licensed under the MIT license found in the 37 | * LICENSE file in the root directory of this source tree. 38 | */ 39 | -------------------------------------------------------------------------------- /build/static/js/main.c8bd8dfa.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpsocktest=this.webpackJsonpsocktest||[]).push([[0],{384:function(e,t,n){e.exports=n(658)},655:function(e,t){e.exports="data:image/png;base64,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"},657:function(e,t,n){},658:function(e,t,n){"use strict";n.r(t);var a=n(1),o=n.n(a),c=n(49),r=n.n(c),l=n(13),i=n(34),u=n(352),s=n(351),d=n(308),m=n(345),g=n(350),A=n(95),f=n(59),p=n.n(f),E=n(348),b=n(346),h=n(353),O=n(312),y=n(20),j=n(74),D=n(169),C=n(349),v=n(32),w=n(183),B=n.n(w),Q=n(379),Y=n.n(Q),M=(n(648),n(649),n(650),n(651),n(652),n(653),n(654),n(344)),k=o.a.memo((function(e){return Object(a.useEffect)((function(){Y.a.highlightAll()}),[e.logs]),o.a.createElement(M.a,null,e.logs.map((function(e,t){return o.a.createElement(M.a.Item,{className:"rs-timeline-item-last",key:t},o.a.createElement("p",{style:{color:"#969696"}},e.datetime),o.a.createElement("p",null,e.message),(null===e||void 0===e?void 0:e.payload)?o.a.createElement("div",null,"incoming"===e.dataflow?o.a.createElement(y.a,{icon:"arrow-down2",style:{color:"rgba(224, 142, 0, 1)"}}):o.a.createElement(y.a,{icon:"arrow-up2",style:{color:"rgba(0, 235, 0, 1)"}}),o.a.createElement("pre",{style:{padding:".5em"}},o.a.createElement("code",{className:"language-json"},null===e||void 0===e?void 0:e.payload))):"")})))})),x=n(106),H=n(347),G=n(73),I=n(664),S={host:"localhost:6001",protocols:"",payload:"",activePayload:"0",payloads:[{id:"0",label:"Default",payload:""}],secure:!0,autoConnect:!1,connectionLog:[{datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:"App started"}]},P=Object(I.a)((function(){var e=Object(a.useState)(function(){try{var e=localStorage.getItem("state");if(null===e)return;return JSON.parse(e)}catch(t){return}}()||S),t=Object(i.a)(e,2),n=t[0],o=t[1];return Object(a.useEffect)((function(){!function(e){try{var t=JSON.stringify(e);localStorage.setItem("state",t)}catch(n){}}(n)}),[n]),[n,o]})),U=P.Provider,W=P.useTracked,R=null,F=o.a.memo((function(e){var t=W(),n=Object(i.a)(t,2),c=n[0],r=n[1],u=Object(a.useRef)();return o.a.createElement(x.a,{placement:"bottom",trigger:"click",triggerRef:function(e){return R=e},speaker:o.a.createElement(H.a,{title:o.a.createElement("div",null,"Websocket Protocol",o.a.createElement(x.a,{speaker:o.a.createElement(G.a,null,"Enter protocols separated by comma."),trigger:"hover",placement:"bottomEnd"},o.a.createElement("span",{className:"rs-help-block rs-help-block-tooltip",style:{marginTop:0}},o.a.createElement(y.a,{icon:"question-circle2"}))))},o.a.createElement(j.a,{inputRef:u,placeholder:"Enter protocols",defaultValue:c.protocols,onPressEnter:function(){return R.hide()}})),onOpen:function(){return u.current.focus()},onExit:function(){r((function(e){return Object(l.a)(Object(l.a)({},e),{},{protocols:u.current.value})}))}},o.a.createElement(O.a.Button,{color:c.protocols?"violet":null},o.a.createElement(y.a,{icon:"sliders"})))})),K=null,L="",N=o.a.memo((function(e){var t=W(),n=Object(i.a)(t,2),c=n[0],r=n[1],u=Object(a.useState)({connected:!1,connecting:!1}),d=Object(i.a)(u,2),m=d[0],g=d[1],f=Object(a.useRef)(),w=Object(a.useRef)(),Q=Object(a.useRef)({connectionLog:c.connectionLog,retryCount:0,autoConnect:c.autoConnect});Object(a.useEffect)((function(){Q.current.autoConnect=c.autoConnect}),[c.autoConnect]),Object(a.useEffect)((function(){var e;c.payloads.find((function(e){return e.id===c.activePayload}))||r((function(e){return Object(l.a)(Object(l.a)({},e),{},{activePayload:"0"})})),w.current.value=null===(e=c.payloads.find((function(e){return e.id===c.activePayload})))||void 0===e?void 0:e.payload}),[c.activePayload,c.payloads,r]);var Y=function(e){Q.current.connectionLog.unshift(e),r((function(e){return Object(l.a)(Object(l.a)({},e),{},{connectionLog:Object(A.a)(Q.current.connectionLog)})}))},M=function(e){Q.current.retryCount=0,Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Connected to "'.concat(e.target.url,'"')}),g(Object(l.a)(Object(l.a)({},m),{},{connected:!0,connecting:!1})),w.current.focus()},x=function(e){Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Message received from "'.concat(e.origin,'"'),payload:e.data,dataflow:"incoming"})},H=function(e){Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Could not connect to "'.concat(e.target.url,'". You may be able to find more information using inspector.')}),g(Object(l.a)(Object(l.a)({},m),{},{connected:!1,connecting:!1}))},G=function(e){Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Connection closed "'.concat(e.target.url,'"')}),g(Object(l.a)(Object(l.a)({},m),{},{connected:!1,connecting:!1})),I()},I=function(){Q.current.autoConnect&&(Q.current.retryCount>=3?(E.a.warning({title:"Warning",description:"Stopped trying to reconnect after ".concat(Q.current.retryCount," attempts.")}),Q.current.retryCount=0):(Q.current.retryCount=Q.current.retryCount+1,S(),E.a.info({title:"Info",description:"Tried to reconnect ".concat(Q.current.retryCount," times.")})))},S=function(){var e;""===f.current.value?E.a.error({title:"Error",description:"Websocket host is not defined."}):(L="".concat(c.secure?"wss://":"ws://").concat(f.current.value),r((function(e){return Object(l.a)(Object(l.a)({},e),{},{host:f.current.value})})),1!==(null===(e=K)||void 0===e?void 0:e.readyState)&&(Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Connecting to "'.concat(L,'/"')}),g(Object(l.a)(Object(l.a)({},m),{},{connecting:!0})),K=""===c.protocols?new WebSocket(L):new WebSocket(L,c.protocols.replace(/\s+/g,"").split(","))),K.onopen=M,K.onmessage=x,K.onerror=H,K.onclose=G)};return o.a.createElement(b.a,null,o.a.createElement(h.a,null,o.a.createElement(O.a,null,o.a.createElement(O.a.Addon,null,o.a.createElement(y.a,{icon:"circle",style:{color:m.connected?"rgba(0, 235, 0, 1)":"rgba(235, 0, 0, 1)"}})),o.a.createElement(O.a.Button,{style:{width:"60px"},onClick:function(){return r((function(e){return Object(l.a)(Object(l.a)({},e),{},{secure:!c.secure})}))},color:c.secure?"green":"orange"},c.secure?"wss://":"ws://"),o.a.createElement(j.a,{defaultValue:c.host,inputRef:f}),o.a.createElement(F,null),m.connected?o.a.createElement(O.a.Button,{color:"red",onClick:function(){return function(){var e;1===(null===(e=K)||void 0===e?void 0:e.readyState)&&K.close()}()},loading:m.connecting},o.a.createElement(y.a,{icon:"unlink"})," Disconnect"):o.a.createElement(O.a.Button,{color:"blue",onClick:function(){return S()},loading:m.connecting},o.a.createElement(y.a,{icon:"link"})," Connect"))),o.a.createElement(h.a,null,o.a.createElement(B.a,{appearance:"tabs",activeKey:c.activePayload,onSelect:function(e){if("add_new"===e){var t=Math.max.apply(Math,Object(A.a)(c.payloads.map((function(e){return e.id}))))+1;r((function(e){return Object(l.a)(Object(l.a)({},e),{},{activePayload:"".concat(t),payloads:[].concat(Object(A.a)(e.payloads),[{id:"".concat(t),label:"Payload ".concat(t),payload:""}])})}))}else e&&(w.current.value=c.payloads.find((function(t){return t.id===e})).payload,r((function(t){return Object(l.a)(Object(l.a)({},t),{},{activePayload:e})})))}},c.payloads.map((function(e){return o.a.createElement(B.a.Item,{key:e.id,eventKey:e.id},e.label,"0"!==e.id?o.a.createElement(D.a,{circle:!0,color:"red",appearance:"link",size:"xs",onClick:function(t){var n=Object(A.a)(c.payloads);n.splice(n.map((function(e){return e.id})).indexOf(e.id),1),r((function(e){return Object(l.a)(Object(l.a)({},e),{},{payloads:n})}))},icon:o.a.createElement(y.a,{icon:"close"}),style:{height:"16px",width:"16px",top:"-5px",marginLeft:"4px"}}):"")})),o.a.createElement(B.a.Item,{key:"add_new",eventKey:"add_new",icon:o.a.createElement(y.a,{icon:"plus"}),style:{background:"#292d33",borderRadius:"6px 6px 0 0"}}," Add New")),o.a.createElement("br",null),o.a.createElement(j.a,{style:{borderColor:m.connected?"rgba(0, 235, 0, 1)":""},inputRef:w,componentClass:"textarea",rows:6,placeholder:"Payload to send"}),o.a.createElement(s.a,{gutter:16,className:"show-grid"},o.a.createElement(C.a,{xs:24,sm:24,md:8,lg:8},o.a.createElement(v.a,{appearance:"ghost",block:!0,color:"orange",onClick:function(){w.current.value="",r((function(e){return Object(l.a)(Object(l.a)({},e),{},{payloads:c.payloads.map((function(e){return e.id===c.activePayload?Object(l.a)(Object(l.a)({},e),{},{payload:""}):e}))})}))}},o.a.createElement(y.a,{icon:"eraser"})," Clear Payload")),o.a.createElement(C.a,{xs:24,sm:24,md:8,lg:8},o.a.createElement(v.a,{appearance:"ghost",block:!0,onClick:function(){r((function(e){return Object(l.a)(Object(l.a)({},e),{},{payloads:c.payloads.map((function(e){return e.id===c.activePayload?Object(l.a)(Object(l.a)({},e),{},{payload:w.current.value}):e}))})}))}},o.a.createElement(y.a,{icon:"save"})," Save Payload")),o.a.createElement(C.a,{xs:24,sm:24,md:8,lg:8},o.a.createElement(v.a,{appearance:"primary",block:!0,onClick:function(){return function(e){var t;switch(r((function(t){return Object(l.a)(Object(l.a)({},t),{},{payloads:c.payloads.map((function(t){return t.id===c.activePayload?Object(l.a)(Object(l.a)({},t),{},{payload:e}):t}))})})),null===(t=K)||void 0===t?void 0:t.readyState){case 1:if(e){K.send(e),Y({datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:'Payload send to "'.concat(L,'"'),payload:e,dataflow:"outgoing"});break}E.a.error({title:"Error",description:"Payload is empty."});break;default:E.a.error({title:"Error",description:"Websocket disconnected."})}}(w.current.value)}},o.a.createElement(y.a,{icon:"realtime"})," Send Payload")))),o.a.createElement(h.a,{header:o.a.createElement("div",null,o.a.createElement(y.a,{icon:"building2"})," Connection Log",o.a.createElement(v.a,{color:"red",onClick:function(){return Q.current.connectionLog=[{datetime:p()().format("YYYY-MM-DD hh:mm:ss A"),message:"App started"}],void r((function(e){return Object(l.a)(Object(l.a)({},e),{},{connectionLog:Q.current.connectionLog})}))},style:{float:"right"}},o.a.createElement(y.a,{icon:"trash"})," Clear Log"))},o.a.createElement(k,{logs:c.connectionLog})))})),Z=o.a.memo((function(e){var t=W(),a=Object(i.a)(t,2),c=a[0],r=a[1];return o.a.createElement(u.a,{fluid:!0},o.a.createElement(s.a,null,o.a.createElement(d.a,null,o.a.createElement(d.a.Header,null,o.a.createElement("a",{target:"_blank",className:"navbar-brand logo",rel:"noopener noreferrer",href:"https://github.com/rahulhaque/websocket-tester-react"},o.a.createElement("img",{src:n(655),alt:"logo",height:"25px"})," ",o.a.createElement("b",null,"Socktest"))),o.a.createElement(d.a.Body,null,o.a.createElement(m.a,{pullRight:!0},"Auto Reconnect",o.a.createElement(g.a,{style:{margin:"16px 20px",width:"50px"},checkedChildren:"On",unCheckedChildren:"Off",checked:c.autoConnect,onChange:function(){return r((function(e){return Object(l.a)(Object(l.a)({},e),{},{autoConnect:!c.autoConnect})}))}}))))),o.a.createElement(s.a,null,o.a.createElement(N,null)))})),q=(n(656),n(657),function(){return o.a.createElement(U,null,o.a.createElement(Z,null))}),z=n(243),J=n(663);z.a.use(J.a).init({resources:{en:{translation:{welcome:"Welcome to React"}}},lng:"en",keySeparator:!1,interpolation:{escapeValue:!1}});z.a;var X=Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));function T(e,t){navigator.serviceWorker.register(e).then((function(e){e.onupdatefound=function(){var n=e.installing;null!=n&&(n.onstatechange=function(){"installed"===n.state&&(navigator.serviceWorker.controller?(console.log("New content is available and will be used when all tabs for this page are closed. See https://bit.ly/CRA-PWA."),t&&t.onUpdate&&t.onUpdate(e)):(console.log("Content is cached for offline use."),t&&t.onSuccess&&t.onSuccess(e)))})}})).catch((function(e){console.error("Error during service worker registration:",e)}))}r.a.render(o.a.createElement(o.a.StrictMode,null,o.a.createElement(q,null)),document.getElementById("root")),function(e){if("serviceWorker"in navigator){if(new URL(".",window.location.href).origin!==window.location.origin)return;window.addEventListener("load",(function(){var t="".concat(".","/service-worker.js");X?(!function(e,t){fetch(e,{headers:{"Service-Worker":"script"}}).then((function(n){var a=n.headers.get("content-type");404===n.status||null!=a&&-1===a.indexOf("javascript")?navigator.serviceWorker.ready.then((function(e){e.unregister().then((function(){window.location.reload()}))})):T(e,t)})).catch((function(){console.log("No internet connection found. App is running in offline mode.")}))}(t,e),navigator.serviceWorker.ready.then((function(){console.log("This web app is being served cache-first by a service worker. To learn more, visit https://bit.ly/CRA-PWA")}))):T(t,e)}))}}()}},[[384,1,2]]]); 2 | //# sourceMappingURL=main.c8bd8dfa.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/main.c8bd8dfa.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["logo.png","components/LogItem.jsx","Store.js","Helpers.js","components/PayloadPopup.jsx","components/WsClient.jsx","layouts/AppLayout.jsx","App.jsx","locale/i18n.js","locale/translations/en.js","serviceWorker.js","index.js"],"names":["module","exports","React","memo","props","useEffect","Prism","highlightAll","logs","Timeline","map","item","index","Item","className","key","style","color","datetime","message","payload","dataflow","Icon","icon","padding","globalState","host","protocols","activePayload","payloads","id","label","secure","autoConnect","connectionLog","dayjs","format","createContainer","useState","serialisedState","localStorage","getItem","JSON","parse","error","loadState","processedState","setProcessedState","state","stringify","setItem","saveState","Provider","useTracked","payloadPopupTrigger","setState","useRef","Whisper","placement","trigger","triggerRef","ref","speaker","Popover","title","Tooltip","marginTop","Input","inputRef","placeholder","defaultValue","onPressEnter","hide","onOpen","current","focus","onExit","prev","value","InputGroup","Button","websocket","wsHost","connected","connecting","connection","setConnection","stateRef","retryCount","find","updateLog","log","unshift","event","target","url","onMessage","origin","data","onError","onClose","reconnect","Notification","warning","description","connect","info","readyState","WebSocket","replace","split","onopen","onmessage","onerror","onclose","PanelGroup","Panel","Addon","width","onClick","close","disconnect","loading","appearance","activeKey","onSelect","max","Math","eventKey","IconButton","circle","size","slicedPayloads","splice","indexOf","height","top","marginLeft","background","borderRadius","borderColor","componentClass","rows","Row","gutter","Col","xs","sm","md","lg","block","send","sendMessage","header","float","Grid","fluid","Navbar","Header","rel","href","src","require","alt","process","Body","Nav","pullRight","Toggle","margin","checkedChildren","unCheckedChildren","checked","onChange","App","i18n","use","initReactI18next","init","resources","en","translation","welcome","lng","keySeparator","interpolation","escapeValue","isLocalhost","Boolean","window","location","hostname","match","registerValidSW","swUrl","config","navigator","serviceWorker","register","then","registration","onupdatefound","installingWorker","installing","onstatechange","controller","console","onUpdate","onSuccess","catch","ReactDOM","render","StrictMode","document","getElementById","URL","addEventListener","fetch","headers","response","contentType","get","status","ready","unregister","reload","checkValidServiceWorker"],"mappings":"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","file":"static/js/main.c8bd8dfa.chunk.js","sourcesContent":["module.exports = \"data:image/png;base64,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\"","import React, { useEffect } from 'react';\r\nimport PropTypes from 'prop-types';\r\nimport Prism from 'prismjs';\r\n\r\nimport {\r\n Icon,\r\n Timeline,\r\n} from 'rsuite';\r\n\r\nconst LogItem = (props) => {\r\n\r\n useEffect(() => {\r\n Prism.highlightAll();\r\n }, [props.logs]);\r\n\r\n return (\r\n \r\n {\r\n props.logs.map((item, index) => {\r\n return \r\n

{item.datetime}

\r\n

{item.message}

\r\n {\r\n item?.payload ?\r\n
\r\n {\r\n item.dataflow === 'incoming' ?\r\n :\r\n \r\n }\r\n
\r\n                    \r\n                      {item?.payload}\r\n                    \r\n                  
\r\n
: \"\"\r\n }\r\n \r\n })\r\n }\r\n
\r\n )\r\n}\r\n\r\nLogItem.propTypes = {\r\n logs: PropTypes.array\r\n};\r\n\r\nexport default React.memo(LogItem);\r\n","import { useState, useEffect } from 'react';\r\nimport { createContainer } from 'react-tracked';\r\nimport dayjs from 'dayjs';\r\n\r\nimport { loadState, saveState } from './Helpers';\r\n\r\nconst globalState = {\r\n // Declare your global variables here\r\n host: 'localhost:6001',\r\n protocols: '',\r\n payload: '',\r\n activePayload: '0',\r\n payloads: [\r\n {id: '0', label: 'Default', payload: ''}\r\n ],\r\n secure: true,\r\n autoConnect: false,\r\n connectionLog: [{\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `App started`\r\n }]\r\n};\r\n\r\n// Returns state from localstorage if exists\r\nconst useLocalState = () => {\r\n const [processedState, setProcessedState] = useState((loadState() || globalState));\r\n useEffect(() => {\r\n saveState(processedState);\r\n }, [processedState]);\r\n return [processedState, setProcessedState];\r\n};\r\n\r\nexport const { Provider, useTracked } = createContainer(useLocalState);\r\n","export const loadState = () => {\r\n try {\r\n const serialisedState = localStorage.getItem('state');\r\n if (serialisedState === null) {\r\n return undefined;\r\n }\r\n return JSON.parse(serialisedState);\r\n } catch (error) {\r\n return undefined;\r\n }\r\n};\r\n\r\nexport const saveState = (state) => {\r\n try {\r\n const serialisedState = JSON.stringify(state);\r\n localStorage.setItem('state', serialisedState);\r\n } catch (error) {\r\n // Ignore write errors.\r\n }\r\n};\r\n\r\n// Get item from localstorage\r\nexport const getItem = (item, def = undefined) => {\r\n try {\r\n const localItem = localStorage.getItem(item);\r\n if (localItem === null) {\r\n return def;\r\n }\r\n return JSON.parse(localItem);\r\n }\r\n catch (e) {\r\n return undefined;\r\n }\r\n};\r\n\r\n// Save item in localstorage\r\nexport const setItem = (item, value) => {\r\n localStorage.setItem(item, JSON.stringify(value));\r\n};\r\n\r\n// Remove item from localstorage\r\nexport const removeItem = (item) => {\r\n localStorage.removeItem(item);\r\n};\r\n\r\n// Check if user logged in from localstorage\r\nexport const isLoggedIn = () => {\r\n return getItem('access_token') && getItem('token_created') && getItem('expires_in');\r\n};\r\n","import React, { useRef } from 'react';\r\n\r\nimport { Whisper, Popover, Tooltip, Input, InputGroup, Icon } from 'rsuite';\r\n\r\nimport { useTracked } from './../Store';\r\n\r\nlet payloadPopupTrigger = null;\r\n\r\nconst PayloadPopup = (props) => {\r\n\r\n const [state, setState] = useTracked();\r\n\r\n const protocols = useRef();\r\n\r\n return (\r\n (payloadPopupTrigger = ref)}\r\n speaker={\r\n \r\n Websocket Protocol\r\n Enter protocols separated by comma.\r\n }\r\n trigger=\"hover\"\r\n placement=\"bottomEnd\"\r\n >\r\n \r\n \r\n \r\n \r\n \r\n }>\r\n payloadPopupTrigger.hide()} />\r\n \r\n }\r\n onOpen={() => protocols.current.focus()}\r\n onExit={() => {\r\n setState(prev => ({ ...prev, protocols: protocols.current.value }))\r\n }}\r\n >\r\n \r\n \r\n \r\n )\r\n};\r\n\r\nexport default React.memo(PayloadPopup);\r\n","import React, { useState, useRef, useEffect } from 'react';\r\nimport dayjs from 'dayjs';\r\n\r\nimport {\r\n Icon,\r\n Input,\r\n InputGroup,\r\n Panel,\r\n PanelGroup,\r\n Button,\r\n Notification,\r\n IconButton,\r\n Row,\r\n Col\r\n} from 'rsuite';\r\nimport ResponsiveNav from '@rsuite/responsive-nav';\r\n\r\nimport LogItem from './LogItem';\r\nimport PayloadPopup from './PayloadPopup';\r\n\r\nimport { useTracked } from './../Store';\r\n\r\nlet websocket = null;\r\nlet wsHost = '';\r\n\r\nconst WsClient = (props) => {\r\n\r\n const [state, setState] = useTracked();\r\n const [connection, setConnection] = useState({ connected: false, connecting: false });\r\n\r\n const host = useRef();\r\n const payload = useRef();\r\n const stateRef = useRef({\r\n connectionLog: state.connectionLog,\r\n retryCount: 0,\r\n autoConnect: state.autoConnect\r\n });\r\n\r\n useEffect(() => {\r\n stateRef.current.autoConnect = state.autoConnect;\r\n }, [state.autoConnect]);\r\n\r\n useEffect(() => {\r\n let currentPayload = state.payloads.find(payload => payload.id === state.activePayload);\r\n if (!currentPayload) {\r\n setState(prev => ({ ...prev, activePayload: '0' }));\r\n }\r\n payload.current.value = state.payloads.find(payload => payload.id === state.activePayload)?.payload;\r\n }, [state.activePayload, state.payloads, setState]);\r\n\r\n const updateLog = (log) => {\r\n stateRef.current.connectionLog.unshift(log);\r\n setState(prev => ({ ...prev, connectionLog: [...stateRef.current.connectionLog] }));\r\n };\r\n\r\n const clearLog = () => {\r\n stateRef.current.connectionLog = [{\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `App started`\r\n }];\r\n setState(prev => ({\r\n ...prev, connectionLog: stateRef.current.connectionLog\r\n }));\r\n };\r\n\r\n const onOpen = (event) => {\r\n stateRef.current.retryCount = 0;\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Connected to \"${event.target.url}\"`\r\n });\r\n setConnection({ ...connection, connected: true, connecting: false });\r\n payload.current.focus();\r\n };\r\n\r\n const onMessage = (event) => {\r\n // console.log(event);\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Message received from \"${event.origin}\"`,\r\n payload: event.data,\r\n dataflow: 'incoming'\r\n });\r\n };\r\n\r\n const onError = (event) => {\r\n // Error handling\r\n // console.error(event);\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Could not connect to \"${event.target.url}\". You may be able to find more information using inspector.`\r\n });\r\n setConnection({ ...connection, connected: false, connecting: false });\r\n };\r\n\r\n const onClose = (event) => {\r\n // Close handling\r\n // console.log(event);\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Connection closed \"${event.target.url}\"`\r\n });\r\n setConnection({ ...connection, connected: false, connecting: false });\r\n reconnect();\r\n };\r\n\r\n const reconnect = () => {\r\n if (stateRef.current.autoConnect) {\r\n if (stateRef.current.retryCount >= 3) {\r\n Notification.warning({ title: 'Warning', description: `Stopped trying to reconnect after ${stateRef.current.retryCount} attempts.` });\r\n stateRef.current.retryCount = 0;\r\n }\r\n else {\r\n stateRef.current.retryCount = stateRef.current.retryCount + 1;\r\n connect();\r\n Notification.info({ title: 'Info', description: `Tried to reconnect ${stateRef.current.retryCount} times.` });\r\n }\r\n }\r\n };\r\n\r\n const connect = () => {\r\n if (host.current.value === '') {\r\n Notification.error({ title: 'Error', description: 'Websocket host is not defined.' });\r\n }\r\n else {\r\n wsHost = `${state.secure ? 'wss://' : 'ws://'}${host.current.value}`;\r\n\r\n setState(prev => ({ ...prev, host: host.current.value }));\r\n if (websocket?.readyState !== 1) {\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Connecting to \"${wsHost}/\"`\r\n });\r\n setConnection({ ...connection, connecting: true });\r\n\r\n if (state.protocols === '') {\r\n websocket = new WebSocket(wsHost);\r\n }\r\n else {\r\n websocket = new WebSocket(wsHost, state.protocols.replace(/\\s+/g, '').split(','));\r\n }\r\n }\r\n\r\n websocket.onopen = onOpen;\r\n websocket.onmessage = onMessage;\r\n websocket.onerror = onError;\r\n websocket.onclose = onClose;\r\n }\r\n };\r\n\r\n const disconnect = () => {\r\n if (websocket?.readyState === 1) {\r\n websocket.close();\r\n }\r\n };\r\n\r\n const sendMessage = (message) => {\r\n // console.log(websocket?.readyState);\r\n setState(prev => ({\r\n ...prev, payloads: state.payloads.map(item => {\r\n if (item.id === state.activePayload) {\r\n return { ...item, payload: message };\r\n }\r\n return item;\r\n })\r\n }));\r\n switch (websocket?.readyState) {\r\n case 1:\r\n if (message) {\r\n websocket.send(message);\r\n updateLog({\r\n datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'),\r\n message: `Payload send to \"${wsHost}\"`,\r\n payload: message,\r\n dataflow: 'outgoing'\r\n });\r\n break;\r\n }\r\n\r\n Notification.error({ title: 'Error', description: 'Payload is empty.' });\r\n break;\r\n\r\n default:\r\n Notification.error({ title: 'Error', description: 'Websocket disconnected.' });\r\n break;\r\n }\r\n\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n setState(prev => ({ ...prev, secure: !state.secure }))}\r\n color={state.secure ? 'green' : 'orange'}\r\n >\r\n {state.secure ? 'wss://' : 'ws://'}\r\n \r\n \r\n \r\n {\r\n connection.connected ? (\r\n disconnect()}\r\n loading={connection.connecting}\r\n >\r\n Disconnect\r\n \r\n ) : (\r\n connect()}\r\n loading={connection.connecting}\r\n >\r\n Connect\r\n \r\n )\r\n }\r\n \r\n \r\n \r\n {\r\n\r\n if (key === 'add_new') {\r\n let max = Math.max(...state.payloads.map(item => item.id)) + 1;\r\n\r\n setState(prev => ({\r\n ...prev,\r\n activePayload: `${max}`,\r\n payloads: [...prev.payloads, {\r\n id: `${max}`,\r\n label: `Payload ${max}`,\r\n payload: ``\r\n }]\r\n }));\r\n }\r\n else if (key) {\r\n payload.current.value = state.payloads.find(payload => payload.id === key).payload\r\n setState(prev => ({ ...prev, activePayload: key }));\r\n }\r\n\r\n }}>\r\n {\r\n state.payloads.map(payload => {\r\n return \r\n {payload.label}\r\n {\r\n payload.id !== '0' ?\r\n {\r\n const slicedPayloads = [...state.payloads];\r\n slicedPayloads.splice(\r\n slicedPayloads.map(payload => payload.id).indexOf(payload.id), 1\r\n );\r\n setState(prev => ({\r\n ...prev,\r\n payloads: slicedPayloads\r\n }))\r\n }}\r\n icon={}\r\n style={{ height: '16px', width: '16px', top: '-5px', marginLeft: '4px' }}>\r\n : ''\r\n }\r\n \r\n })\r\n }\r\n }\r\n style={{ background: '#292d33', borderRadius: '6px 6px 0 0' }}> Add New\r\n \r\n \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n Connection Log\r\n \r\n \r\n }>\r\n \r\n \r\n
\r\n )\r\n}\r\n\r\nexport default React.memo(WsClient);\r\n","import React from 'react';\r\n\r\nimport {\r\n Grid,\r\n Row,\r\n Nav,\r\n Navbar,\r\n Toggle,\r\n} from 'rsuite';\r\n\r\nimport WsClient from './../components/WsClient';\r\n\r\nimport { useTracked } from './../Store';\r\n\r\nconst app_name = process.env.REACT_APP_APP_NAME;\r\n\r\nconst AppLayout = (props) => {\r\n\r\n const [state, setState] = useTracked();\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \"logo\" {app_name}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default React.memo(AppLayout)\r\n","import React from 'react';\n\nimport AppLayout from './layouts/AppLayout';\n\nimport { Provider } from './Store';\n\nimport 'rsuite/dist/styles/rsuite-dark.css';\nimport './App.css';\n\nconst App = () => (\n \n \n \n);\n\nexport default App;\n","import i18n from 'i18next';\r\nimport { initReactI18next } from 'react-i18next';\r\n\r\n// Add translation file in 'translation' folder\r\n// Import them here\r\nimport en from './translations/en';\r\n\r\ni18n\r\n .use(initReactI18next) // passes i18n down to react-i18next\r\n .init({\r\n resources: {\r\n en: en,\r\n // Add imported translation\r\n },\r\n lng: 'en', // Initial locale\r\n keySeparator: false, // we do not use keys in form messages.welcome\r\n interpolation: {\r\n escapeValue: false // react already safes from xss\r\n }\r\n });\r\n\r\nexport default i18n;\r\n","export default {\r\n translation: {\r\n welcome: 'Welcome to React'\r\n }\r\n}\r\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' },\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready\n .then(registration => {\n registration.unregister();\n })\n .catch(error => {\n console.error(error.message);\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport './locale/i18n';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.register();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /build/static/js/runtime-main.07971bfb.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,l,f=t[0],i=t[1],a=t[2],p=0,s=[];p0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "devDependencies": { 43 | "babel-plugin-prismjs": "^2.0.1", 44 | "customize-cra": "^1.0.0", 45 | "less": "^3.11.3", 46 | "less-loader": "^6.1.1", 47 | "react-app-rewired": "^2.1.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /public/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/favicon-16x16.png -------------------------------------------------------------------------------- /public/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/favicon-32x32.png -------------------------------------------------------------------------------- /public/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/assets/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | Socktest 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Socktest", 3 | "name": "Socktest", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "./assets/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "./assets/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#1a1d24", 24 | "background_color": "#1a1d24" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /screenshots/screen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/screenshots/screen_1.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .navbar-brand { 7 | padding: 18px 20px; 8 | display: inline-block; 9 | } 10 | 11 | .navbar-brand:hover, .navbar-brand:focus { 12 | text-decoration: none; 13 | } 14 | 15 | /* For iPhone 5/SE */ 16 | textarea.rs-input { 17 | min-width: 200px; 18 | } 19 | 20 | .rs-notification { 21 | top: 12px !important; 22 | right: 12px; 23 | bottom: 12px; 24 | max-width: 294px; 25 | } 26 | 27 | .rs-notification-item-content { 28 | padding: 15px; 29 | } 30 | 31 | .show-grid [class*=rs-col-] { 32 | padding-top: 10px; 33 | margin-top: 10px; 34 | } 35 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AppLayout from './layouts/AppLayout'; 4 | 5 | import { Provider } from './Store'; 6 | 7 | import 'rsuite/dist/styles/rsuite-dark.css'; 8 | import './App.css'; 9 | 10 | const App = () => ( 11 | 12 | 13 | 14 | ); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/Helpers.js: -------------------------------------------------------------------------------- 1 | export const loadState = () => { 2 | try { 3 | const serialisedState = localStorage.getItem('socktest-state'); 4 | if (serialisedState === null) { 5 | return undefined; 6 | } 7 | return JSON.parse(serialisedState); 8 | } catch (error) { 9 | return undefined; 10 | } 11 | }; 12 | 13 | export const saveState = (state) => { 14 | try { 15 | const serialisedState = JSON.stringify(state); 16 | localStorage.setItem('socktest-state', serialisedState); 17 | } catch (error) { 18 | // Ignore write errors. 19 | } 20 | }; 21 | 22 | // Get item from localstorage 23 | export const getItem = (item, def = undefined) => { 24 | try { 25 | const localItem = localStorage.getItem(item); 26 | if (localItem === null) { 27 | return def; 28 | } 29 | return JSON.parse(localItem); 30 | } 31 | catch (e) { 32 | return undefined; 33 | } 34 | }; 35 | 36 | // Save item in localstorage 37 | export const setItem = (item, value) => { 38 | localStorage.setItem(item, JSON.stringify(value)); 39 | }; 40 | 41 | // Remove item from localstorage 42 | export const removeItem = (item) => { 43 | localStorage.removeItem(item); 44 | }; 45 | 46 | // Check if user logged in from localstorage 47 | export const isLoggedIn = () => { 48 | return getItem('access_token') && getItem('token_created') && getItem('expires_in'); 49 | }; 50 | -------------------------------------------------------------------------------- /src/Routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | HashRouter, 4 | Route, 5 | Switch, 6 | Redirect 7 | } from 'react-router-dom'; 8 | 9 | import AppLayout from './layouts/AppLayout'; 10 | 11 | import { isLoggedIn } from './Helpers'; 12 | 13 | export const PrivateRoute = ({ component: Component, ...rest }) => ( 14 | 17 | isLoggedIn() ? ( 18 | 19 | ) : ( 20 | 26 | ) 27 | } 28 | /> 29 | ); 30 | 31 | export const GuestRoute = ({ component: Component, ...rest }) => ( 32 | 35 | !isLoggedIn() ? ( 36 | 37 | ) : ( 38 | 44 | ) 45 | } 46 | /> 47 | ); 48 | 49 | const Routes = () => ( 50 | 51 | 52 | 53 | 54 | 55 | ); 56 | 57 | export default React.memo(Routes); 58 | -------------------------------------------------------------------------------- /src/Store.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { createContainer } from 'react-tracked'; 3 | import dayjs from 'dayjs'; 4 | 5 | import { loadState, saveState } from './Helpers'; 6 | 7 | const globalState = { 8 | // Declare your global variables here 9 | host: 'localhost:6001', 10 | protocols: '', 11 | payload: '', 12 | activePayload: '0', 13 | payloads: [ 14 | {id: '0', label: 'Default', payload: ''} 15 | ], 16 | secure: true, 17 | autoConnect: false, 18 | connectionLog: [{ 19 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 20 | message: `App started` 21 | }] 22 | }; 23 | 24 | // Returns state from localstorage if exists 25 | const useLocalState = () => { 26 | const [processedState, setProcessedState] = useState((loadState() || globalState)); 27 | useEffect(() => { 28 | saveState(processedState); 29 | }, [processedState]); 30 | return [processedState, setProcessedState]; 31 | }; 32 | 33 | export const { Provider, useTracked } = createContainer(useLocalState); 34 | -------------------------------------------------------------------------------- /src/components/LogItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Prism from 'prismjs'; 4 | 5 | import { 6 | Icon, 7 | Timeline, 8 | } from 'rsuite'; 9 | 10 | const LogItem = (props) => { 11 | 12 | useEffect(() => { 13 | Prism.highlightAll(); 14 | }, [props.logs]); 15 | 16 | return ( 17 | 18 | { 19 | props.logs.map((item, index) => { 20 | return 24 |

{item.datetime}

25 |

{item.message}

26 | { 27 | item?.payload ? 28 |
29 | { 30 | item.dataflow === 'incoming' ? 31 | : 32 | 33 | } 34 |
35 |                     
36 |                       {item?.payload}
37 |                     
38 |                   
39 |
: "" 40 | } 41 |
42 | }) 43 | } 44 |
45 | ) 46 | } 47 | 48 | LogItem.propTypes = { 49 | logs: PropTypes.array 50 | }; 51 | 52 | export default React.memo(LogItem); 53 | -------------------------------------------------------------------------------- /src/components/PayloadPopup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | 3 | import { Whisper, Popover, Tooltip, Input, InputGroup, Icon } from 'rsuite'; 4 | 5 | import { useTracked } from './../Store'; 6 | 7 | let payloadPopupTrigger = null; 8 | 9 | const PayloadPopup = (props) => { 10 | 11 | const [state, setState] = useTracked(); 12 | 13 | const protocols = useRef(); 14 | 15 | return ( 16 | (payloadPopupTrigger = ref)} 20 | speaker={ 21 | 23 | Websocket Protocol 24 | Enter protocols separated by comma. 27 | } 28 | trigger="hover" 29 | placement="bottomEnd" 30 | > 31 | 32 | 33 | 34 | 35 | 36 | }> 37 | payloadPopupTrigger.hide()} /> 38 | 39 | } 40 | onOpen={() => protocols.current.focus()} 41 | onExit={() => { 42 | setState(prev => ({ ...prev, protocols: protocols.current.value })) 43 | }} 44 | > 45 | 46 | 47 | 48 | ) 49 | }; 50 | 51 | export default React.memo(PayloadPopup); 52 | -------------------------------------------------------------------------------- /src/components/WsClient.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react'; 2 | import dayjs from 'dayjs'; 3 | 4 | import { 5 | Icon, 6 | Input, 7 | InputGroup, 8 | Panel, 9 | PanelGroup, 10 | Button, 11 | Notification, 12 | IconButton, 13 | Row, 14 | Col 15 | } from 'rsuite'; 16 | import ResponsiveNav from '@rsuite/responsive-nav'; 17 | 18 | import LogItem from './LogItem'; 19 | import PayloadPopup from './PayloadPopup'; 20 | 21 | import { useTracked } from './../Store'; 22 | 23 | let websocket = null; 24 | let wsHost = ''; 25 | 26 | const WsClient = (props) => { 27 | 28 | const [state, setState] = useTracked(); 29 | const [connection, setConnection] = useState({ connected: false, connecting: false }); 30 | 31 | const host = useRef(); 32 | const payload = useRef(); 33 | const stateRef = useRef({ 34 | connectionLog: state.connectionLog, 35 | retryCount: 0, 36 | autoConnect: state.autoConnect 37 | }); 38 | 39 | useEffect(() => { 40 | stateRef.current.autoConnect = state.autoConnect; 41 | }, [state.autoConnect]); 42 | 43 | useEffect(() => { 44 | let currentPayload = state.payloads.find(payload => payload.id === state.activePayload); 45 | if (!currentPayload) { 46 | setState(prev => ({ ...prev, activePayload: '0' })); 47 | } 48 | payload.current.value = state.payloads.find(payload => payload.id === state.activePayload)?.payload; 49 | }, [state.activePayload, state.payloads, setState]); 50 | 51 | const updateLog = (log) => { 52 | stateRef.current.connectionLog.unshift(log); 53 | setState(prev => ({ ...prev, connectionLog: [...stateRef.current.connectionLog] })); 54 | }; 55 | 56 | const clearLog = () => { 57 | stateRef.current.connectionLog = [{ 58 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 59 | message: `App started` 60 | }]; 61 | setState(prev => ({ 62 | ...prev, connectionLog: stateRef.current.connectionLog 63 | })); 64 | }; 65 | 66 | const onOpen = (event) => { 67 | stateRef.current.retryCount = 0; 68 | updateLog({ 69 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 70 | message: `Connected to "${event.target.url}"` 71 | }); 72 | setConnection({ ...connection, connected: true, connecting: false }); 73 | payload.current.focus(); 74 | }; 75 | 76 | const onMessage = (event) => { 77 | // console.log(event); 78 | updateLog({ 79 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 80 | message: `Message received from "${event.origin}"`, 81 | payload: event.data, 82 | dataflow: 'incoming' 83 | }); 84 | }; 85 | 86 | const onError = (event) => { 87 | // Error handling 88 | // console.error(event); 89 | updateLog({ 90 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 91 | message: `Could not connect to "${event.target.url}". You may be able to find more information using inspector.` 92 | }); 93 | setConnection({ ...connection, connected: false, connecting: false }); 94 | }; 95 | 96 | const onClose = (event) => { 97 | // Close handling 98 | // console.log(event); 99 | updateLog({ 100 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 101 | message: `Connection closed "${event.target.url}"` 102 | }); 103 | setConnection({ ...connection, connected: false, connecting: false }); 104 | reconnect(); 105 | }; 106 | 107 | const reconnect = () => { 108 | if (stateRef.current.autoConnect) { 109 | if (stateRef.current.retryCount >= 3) { 110 | Notification.warning({ title: 'Warning', description: `Stopped trying to reconnect after ${stateRef.current.retryCount} attempts.` }); 111 | stateRef.current.retryCount = 0; 112 | } 113 | else { 114 | stateRef.current.retryCount = stateRef.current.retryCount + 1; 115 | connect(); 116 | Notification.info({ title: 'Info', description: `Tried to reconnect ${stateRef.current.retryCount} times.` }); 117 | } 118 | } 119 | }; 120 | 121 | const connect = () => { 122 | if (host.current.value === '') { 123 | Notification.error({ title: 'Error', description: 'Websocket host is not defined.' }); 124 | } 125 | else { 126 | wsHost = `${state.secure ? 'wss://' : 'ws://'}${host.current.value}`; 127 | 128 | setState(prev => ({ ...prev, host: host.current.value })); 129 | if (websocket?.readyState !== 1) { 130 | updateLog({ 131 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 132 | message: `Connecting to "${wsHost}/"` 133 | }); 134 | setConnection({ ...connection, connecting: true }); 135 | 136 | if (state.protocols === '') { 137 | websocket = new WebSocket(wsHost); 138 | } 139 | else { 140 | websocket = new WebSocket(wsHost, state.protocols.replace(/\s+/g, '').split(',')); 141 | } 142 | } 143 | 144 | websocket.onopen = onOpen; 145 | websocket.onmessage = onMessage; 146 | websocket.onerror = onError; 147 | websocket.onclose = onClose; 148 | } 149 | }; 150 | 151 | const disconnect = () => { 152 | if (websocket?.readyState === 1) { 153 | websocket.close(); 154 | } 155 | }; 156 | 157 | const sendMessage = (message) => { 158 | // console.log(websocket?.readyState); 159 | setState(prev => ({ 160 | ...prev, payloads: state.payloads.map(item => { 161 | if (item.id === state.activePayload) { 162 | return { ...item, payload: message }; 163 | } 164 | return item; 165 | }) 166 | })); 167 | switch (websocket?.readyState) { 168 | case 1: 169 | if (message) { 170 | websocket.send(message); 171 | updateLog({ 172 | datetime: dayjs().format('YYYY-MM-DD hh:mm:ss A'), 173 | message: `Payload send to "${wsHost}"`, 174 | payload: message, 175 | dataflow: 'outgoing' 176 | }); 177 | break; 178 | } 179 | 180 | Notification.error({ title: 'Error', description: 'Payload is empty.' }); 181 | break; 182 | 183 | default: 184 | Notification.error({ title: 'Error', description: 'Websocket disconnected.' }); 185 | break; 186 | } 187 | 188 | }; 189 | 190 | return ( 191 | 192 | 193 | 194 | 195 | 196 | 197 | setState(prev => ({ ...prev, secure: !state.secure }))} 200 | color={state.secure ? 'green' : 'orange'} 201 | > 202 | {state.secure ? 'wss://' : 'ws://'} 203 | 204 | 205 | 206 | { 207 | connection.connected ? ( 208 | disconnect()} 211 | loading={connection.connecting} 212 | > 213 | Disconnect 214 | 215 | ) : ( 216 | connect()} 219 | loading={connection.connecting} 220 | > 221 | Connect 222 | 223 | ) 224 | } 225 | 226 | 227 | 228 | { 229 | 230 | if (key === 'add_new') { 231 | let max = Math.max(...state.payloads.map(item => item.id)) + 1; 232 | 233 | setState(prev => ({ 234 | ...prev, 235 | activePayload: `${max}`, 236 | payloads: [...prev.payloads, { 237 | id: `${max}`, 238 | label: `Payload ${max}`, 239 | payload: `` 240 | }] 241 | })); 242 | } 243 | else if (key) { 244 | payload.current.value = state.payloads.find(payload => payload.id === key).payload 245 | setState(prev => ({ ...prev, activePayload: key })); 246 | } 247 | 248 | }}> 249 | { 250 | state.payloads.map(payload => { 251 | return 252 | {payload.label} 253 | { 254 | payload.id !== '0' ? 255 | { 260 | const slicedPayloads = [...state.payloads]; 261 | slicedPayloads.splice( 262 | slicedPayloads.map(payload => payload.id).indexOf(payload.id), 1 263 | ); 264 | setState(prev => ({ 265 | ...prev, 266 | payloads: slicedPayloads 267 | })) 268 | }} 269 | icon={} 270 | style={{ height: '16px', width: '16px', top: '-5px', marginLeft: '4px' }}> 271 | : '' 272 | } 273 | 274 | }) 275 | } 276 | } 280 | style={{ background: '#292d33', borderRadius: '6px 6px 0 0' }}> Add New 281 | 282 | 283 |
284 | 293 | 294 | 295 | 307 | 308 | 309 | 320 | 321 | 322 | 323 | 324 | 325 |
326 | 328 | Connection Log 329 | 330 | 331 | }> 332 | 333 | 334 |
335 | ) 336 | } 337 | 338 | export default React.memo(WsClient); 339 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './locale/i18n'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.register(); 18 | -------------------------------------------------------------------------------- /src/layouts/AppLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | Grid, 5 | Row, 6 | Nav, 7 | Navbar, 8 | Toggle, 9 | } from 'rsuite'; 10 | 11 | import WsClient from './../components/WsClient'; 12 | 13 | import { useTracked } from './../Store'; 14 | 15 | const app_name = process.env.REACT_APP_APP_NAME; 16 | 17 | const AppLayout = (props) => { 18 | 19 | const [state, setState] = useTracked(); 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 31 | logo {app_name} 32 | 33 | 34 | 35 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | export default React.memo(AppLayout) 56 | -------------------------------------------------------------------------------- /src/locale/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | 4 | // Add translation file in 'translation' folder 5 | // Import them here 6 | import en from './translations/en'; 7 | 8 | i18n 9 | .use(initReactI18next) // passes i18n down to react-i18next 10 | .init({ 11 | resources: { 12 | en: en, 13 | // Add imported translation 14 | }, 15 | lng: 'en', // Initial locale 16 | keySeparator: false, // we do not use keys in form messages.welcome 17 | interpolation: { 18 | escapeValue: false // react already safes from xss 19 | } 20 | }); 21 | 22 | export default i18n; 23 | -------------------------------------------------------------------------------- /src/locale/translations/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | translation: { 3 | welcome: 'Welcome to React' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/websocket-tester-react/64e776e0f20bcfb5b405d877eb880f665198c586/src/logo.png -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | --------------------------------------------------------------------------------