├── .gitignore ├── assets └── cover.png ├── vite.config.ts ├── src ├── main.tsx ├── index.css ├── favicon.svg ├── App.tsx └── chat-client.tsx ├── index.html ├── package.json ├── tsconfig.json ├── readme.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexkrkn/lambda-websocket-client/HEAD/assets/cover.png -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import reactRefresh from '@vitejs/plugin-react-refresh' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [reactRefresh()] 7 | }) 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-websocket-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "@material-ui/core": "^4.11.4", 11 | "react": "^17.0.0", 12 | "react-dom": "^17.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^17.0.0", 16 | "@types/react-dom": "^17.0.0", 17 | "@vitejs/plugin-react-refresh": "^1.3.1", 18 | "typescript": "^4.1.2", 19 | "vite": "^2.3.3" 20 | } 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": [ 5 | "DOM", 6 | "DOM.Iterable", 7 | "ESNext" 8 | ], 9 | "types": [ 10 | "vite/client" 11 | ], 12 | "allowJs": false, 13 | "skipLibCheck": false, 14 | "esModuleInterop": false, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "ESNext", 19 | "moduleResolution": "Node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "jsx": "react" 24 | }, 25 | "include": [ 26 | "./src" 27 | ] 28 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | How to build a chat using Lambda + WebSocket + API Gateway? (nodejs) 3 | 4 | 5 | # Description 6 | 7 | Source code for the reactjs client from the screencast How to build a chat using Lambda + WebSocket + API Gateway? (nodejs) 8 | 9 | The backend lambda code is [here](https://github.com/alexkrkn/lambda-websocket-server) 10 | 11 | # Run 12 | 13 | - setup and run the [WebSocket](https://github.com/alexkrkn/lambda-websocket-server) 14 | - copy the websocket url as described in the video to [this constant](src/App.tsx#L4) 15 | - run `npm run dev` 🚀 16 | 17 | 18 | Don't forget to subscribe 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present alexkrkn 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. -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback, useRef } from 'react' 2 | import { ChatClient } from './chat-client'; 3 | 4 | const URL = 'ENTER_YOUR_WEBSOCKET_URL_HERE'; 5 | 6 | const App = () => { 7 | 8 | const socket = useRef(null); 9 | const [isConnected, setIsConnected] = useState(false); 10 | const [members, setMembers] = useState([]); 11 | const [chatRows, setChatRows] = useState([]); 12 | 13 | const onSocketOpen = useCallback(() => { 14 | setIsConnected(true); 15 | const name = prompt('Enter your name'); 16 | socket.current?.send(JSON.stringify({ action: 'setName', name })); 17 | }, []); 18 | 19 | const onSocketClose = useCallback(() => { 20 | setMembers([]); 21 | setIsConnected(false); 22 | setChatRows([]); 23 | }, []); 24 | 25 | const onSocketMessage = useCallback((dataStr) => { 26 | const data = JSON.parse(dataStr); 27 | if (data.members) { 28 | setMembers(data.members); 29 | } else if (data.publicMessage) { 30 | setChatRows(oldArray => [...oldArray, {data.publicMessage}]); 31 | } else if (data.privateMessage) { 32 | alert(data.privateMessage); 33 | } else if (data.systemMessage) { 34 | setChatRows(oldArray => [...oldArray, {data.systemMessage}]); 35 | } 36 | }, []); 37 | 38 | const onConnect = useCallback(() => { 39 | if (socket.current?.readyState !== WebSocket.OPEN) { 40 | socket.current = new WebSocket(URL); 41 | socket.current.addEventListener('open', onSocketOpen); 42 | socket.current.addEventListener('close', onSocketClose); 43 | socket.current.addEventListener('message', (event) => { 44 | onSocketMessage(event.data); 45 | }); 46 | } 47 | }, []); 48 | 49 | useEffect(() => { 50 | return () => { 51 | socket.current?.close(); 52 | }; 53 | }, []); 54 | 55 | const onSendPrivateMessage = useCallback((to: string) => { 56 | const message = prompt('Enter private message for ' + to); 57 | socket.current?.send(JSON.stringify({ 58 | action: 'sendPrivate', 59 | message, 60 | to, 61 | })); 62 | }, []); 63 | 64 | const onSendPublicMessage = useCallback(() => { 65 | const message = prompt('Enter public message'); 66 | socket.current?.send(JSON.stringify({ 67 | action: 'sendPublic', 68 | message, 69 | })); 70 | }, []); 71 | 72 | const onDisconnect = useCallback(() => { 73 | if (isConnected) { 74 | socket.current?.close(); 75 | } 76 | }, [isConnected]); 77 | 78 | return ; 87 | 88 | } 89 | 90 | export default App -------------------------------------------------------------------------------- /src/chat-client.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from '@material-ui/core'; 3 | import ListItemText from '@material-ui/core/ListItemText'; 4 | import CssBaseline from '@material-ui/core/CssBaseline'; 5 | import Container from '@material-ui/core/Container'; 6 | import Grid from '@material-ui/core/Grid'; 7 | import List from '@material-ui/core/List'; 8 | import ListItem from '@material-ui/core/ListItem'; 9 | import Paper from '@material-ui/core/Paper'; 10 | 11 | interface Props { 12 | isConnected: boolean; 13 | members: string[]; 14 | chatRows: React.ReactNode[]; 15 | onPublicMessage: () => void; 16 | onPrivateMessage: (to: string) => void; 17 | onConnect: () => void; 18 | onDisconnect: () => void; 19 | } 20 | 21 | export const ChatClient = (props: Props) => { 22 | return ( 23 |
31 | 32 | 33 | 34 | 35 | 36 | {props.members.map(item => 37 | { props.onPrivateMessage(item); }} button> 38 | 39 | 40 | )} 41 | 42 | 43 | 44 | 45 | 46 | 47 |
    52 | {props.chatRows.map((item, i) => 53 |
  • {item}
  • 54 | )} 55 |
56 |
57 | 58 | {props.isConnected && } 59 | {props.isConnected && } 60 | {!props.isConnected && } 61 | 62 |
63 |
72 | 73 | 74 | 75 | 76 |
77 | ) 78 | }; --------------------------------------------------------------------------------