├── chat ├── __init__.py ├── auth.py ├── config.py ├── app.py ├── demo_data.py ├── socketio_signals.py ├── utils.py └── routes.py ├── Procfile ├── client ├── .gitignore ├── build │ ├── robots.txt │ ├── avatars │ │ ├── 0.jpg │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── 8.jpg │ │ ├── 9.jpg │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ └── 12.jpg │ ├── favicon.ico │ ├── welcome-back.png │ ├── asset-manifest.json │ ├── static │ │ ├── js │ │ │ ├── runtime-main.c012fedc.js │ │ │ ├── 2.a950a4d1.chunk.js.LICENSE.txt │ │ │ ├── runtime-main.c012fedc.js.map │ │ │ └── main.e15e5a37.chunk.js │ │ └── css │ │ │ ├── main.c68ddd7d.chunk.css │ │ │ └── main.c68ddd7d.chunk.css.map │ └── index.html ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── avatars │ │ ├── 0.jpg │ │ ├── 1.jpg │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ ├── 12.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── 8.jpg │ │ └── 9.jpg │ ├── welcome-back.png │ └── index.html ├── README.md ├── src │ ├── components │ │ ├── LoadingScreen.jsx │ │ ├── Chat │ │ │ ├── components │ │ │ │ ├── MessageList │ │ │ │ │ ├── components │ │ │ │ │ │ ├── InfoMessage.jsx │ │ │ │ │ │ ├── NoMessages.jsx │ │ │ │ │ │ ├── MessagesLoading.jsx │ │ │ │ │ │ ├── ClockIcon.jsx │ │ │ │ │ │ ├── ReceiverMessage.jsx │ │ │ │ │ │ └── SenderMessage.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── OnlineIndicator.jsx │ │ │ │ ├── ChatList │ │ │ │ │ ├── components │ │ │ │ │ │ ├── ChatListItem │ │ │ │ │ │ │ ├── style.css │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── AvatarImage.jsx │ │ │ │ │ │ ├── ChatIcon.jsx │ │ │ │ │ │ └── Footer.jsx │ │ │ │ │ └── index.jsx │ │ │ │ └── TypingArea.jsx │ │ │ ├── index.jsx │ │ │ └── use-chat-handlers.js │ │ ├── Login │ │ │ ├── style.css │ │ │ └── index.jsx │ │ ├── Navbar.jsx │ │ └── Logo.jsx │ ├── index.jsx │ ├── utils.js │ ├── styles │ │ ├── style-overrides.css │ │ ├── style.css │ │ └── font-face.css │ ├── api.js │ ├── state.js │ ├── App.jsx │ └── hooks.js └── package.json ├── .flake8 ├── repo.json ├── requirements.txt ├── docs ├── YTThumbnail.png ├── screenshot000.png └── screenshot001.png ├── images └── app_preview_image.png ├── .editorconfig ├── app.py ├── Dockerfile ├── app.json ├── LICENSE ├── marketplace.json ├── .gitignore └── README.md /chat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --worker-class eventlet -w 1 app:app -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | !build 3 | .eslintcache -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | F401, 4 | F403, 5 | F405, 6 | max-line-length = 120 7 | -------------------------------------------------------------------------------- /client/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": "https://github.com/redis-developer/basic-redis-chat-app-demo-python" 3 | } 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/requirements.txt -------------------------------------------------------------------------------- /docs/YTThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/docs/YTThumbnail.png -------------------------------------------------------------------------------- /docs/screenshot000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/docs/screenshot000.png -------------------------------------------------------------------------------- /docs/screenshot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/docs/screenshot001.png -------------------------------------------------------------------------------- /client/build/avatars/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/0.jpg -------------------------------------------------------------------------------- /client/build/avatars/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/1.jpg -------------------------------------------------------------------------------- /client/build/avatars/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/2.jpg -------------------------------------------------------------------------------- /client/build/avatars/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/3.jpg -------------------------------------------------------------------------------- /client/build/avatars/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/4.jpg -------------------------------------------------------------------------------- /client/build/avatars/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/5.jpg -------------------------------------------------------------------------------- /client/build/avatars/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/6.jpg -------------------------------------------------------------------------------- /client/build/avatars/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/7.jpg -------------------------------------------------------------------------------- /client/build/avatars/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/8.jpg -------------------------------------------------------------------------------- /client/build/avatars/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/9.jpg -------------------------------------------------------------------------------- /client/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/favicon.ico -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/favicon.ico -------------------------------------------------------------------------------- /client/build/avatars/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/10.jpg -------------------------------------------------------------------------------- /client/build/avatars/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/11.jpg -------------------------------------------------------------------------------- /client/build/avatars/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/avatars/12.jpg -------------------------------------------------------------------------------- /client/public/avatars/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/0.jpg -------------------------------------------------------------------------------- /client/public/avatars/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/1.jpg -------------------------------------------------------------------------------- /client/public/avatars/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/10.jpg -------------------------------------------------------------------------------- /client/public/avatars/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/11.jpg -------------------------------------------------------------------------------- /client/public/avatars/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/12.jpg -------------------------------------------------------------------------------- /client/public/avatars/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/2.jpg -------------------------------------------------------------------------------- /client/public/avatars/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/3.jpg -------------------------------------------------------------------------------- /client/public/avatars/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/4.jpg -------------------------------------------------------------------------------- /client/public/avatars/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/5.jpg -------------------------------------------------------------------------------- /client/public/avatars/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/6.jpg -------------------------------------------------------------------------------- /client/public/avatars/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/7.jpg -------------------------------------------------------------------------------- /client/public/avatars/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/8.jpg -------------------------------------------------------------------------------- /client/public/avatars/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/avatars/9.jpg -------------------------------------------------------------------------------- /images/app_preview_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/images/app_preview_image.png -------------------------------------------------------------------------------- /client/build/welcome-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/build/welcome-back.png -------------------------------------------------------------------------------- /client/public/welcome-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-python/master/client/public/welcome-back.png -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | ## Project setup 4 | 5 | ``` 6 | yarn install 7 | ``` 8 | 9 | ### Compiles and hot-reloads for development 10 | 11 | ``` 12 | yarn start 13 | ``` 14 | 15 | ### Compiles and minifies for production 16 | 17 | ``` 18 | yarn build 19 | ``` 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [Makefile] 4 | indent_style = tab 5 | 6 | [*.{html,py,js}] 7 | charset = utf-8 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.html] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.py] 18 | indent_style = space 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /client/src/components/LoadingScreen.jsx: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import React from "react"; 3 | 4 | export function LoadingScreen() { 5 | return ( 6 |
8 | {message} 9 |
10 | ); 11 | }; 12 | 13 | export default InfoMessage; 14 | -------------------------------------------------------------------------------- /client/src/components/Chat/components/OnlineIndicator.jsx: -------------------------------------------------------------------------------- 1 | const OnlineIndicator = ({ online, hide = false, width = 8, height = 8 }) => { 2 | return ( 3 | 9 | ); 10 | }; 11 | 12 | export default OnlineIndicator; 13 | -------------------------------------------------------------------------------- /chat/auth.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from flask import jsonify, session 4 | 5 | 6 | def auth_middleware(f): 7 | """This decorator will filter out unauthorized connections.""" 8 | 9 | @wraps(f) 10 | def __auth_middleware(*args, **kwargs): 11 | if not session["user"]: 12 | return jsonify(None), 403 13 | return f(*args, **kwargs) 14 | 15 | return __auth_middleware 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Python37 2 | FROM python:3.7 3 | # Copy requirements.txt to the docker image and install packages 4 | COPY requirements.txt / 5 | RUN pip install -r requirements.txt 6 | # Set the WORKDIR to be the folder 7 | COPY . /app 8 | # Expose port 8080 9 | EXPOSE 8080 10 | ENV PORT 8080 11 | WORKDIR /app 12 | # Use gunicorn as the entrypoint 13 | CMD exec gunicorn --bind :$PORT --worker-class eventlet -w 1 app:app 14 | -------------------------------------------------------------------------------- /client/src/components/Chat/components/MessageList/components/NoMessages.jsx: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import React from "react"; 3 | import { CardText } from "react-bootstrap-icons"; 4 | 5 | const NoMessages = () => { 6 | return ( 7 |No messages
10 |{message}
28 |
29 |
Chats
21 |{message}
36 |
37 |
Active
52 |{lastMessage.message}
33 | )} 34 |Sign in to continue
46 |
53 |
6 |
7 |
8 | # Overview video
9 |
10 | Here's a short video that explains the project and how it uses Redis:
11 |
12 | [](https://www.youtube.com/watch?v=miK7xDkDXF0)
13 |
14 | ## Technical Stacks
15 |
16 | - Frontend - _React_, _Socket.IO_
17 | - Backend - _Flask_, _Redis_
18 |
19 | ## How it works?
20 |
21 | ### Initialization
22 |
23 | For simplicity, a key with **total_users** value is checked: if it does not exist, we fill the Redis database with initial data.
24 | `EXISTS total_users` (checks if the key exists)
25 |
26 | The demo data initialization is handled in multiple steps:
27 |
28 | **Creating of demo users:**
29 | We create a new user id: `INCR total_users`. Then we set a user ID lookup key by user name: **_e.g._** `SET username:nick user:1`. And finally, the rest of the data is written to the hash set: **_e.g._** `HSET user:1 username "nick" password "bcrypt_hashed_password"`.
30 |
31 | Additionally, each user is added to the default "General" room. For handling rooms for each user, we have a set that holds the room ids. Here's an example command of how to add the room: **_e.g._** `SADD user:1:rooms "0"`.
32 |
33 | **Populate private messages between users.**
34 | At first, private rooms are created: if a private room needs to be established, for each user a room id: `room:1:2` is generated, where numbers correspond to the user ids in ascending order.
35 |
36 | **_E.g._** Create a private room between 2 users: `SADD user:1:rooms 1:2` and `SADD user:2:rooms 1:2`.
37 |
38 | Then we add messages to this room by writing to a sorted set:
39 |
40 | **_E.g._** `ZADD room:1:2 1615480369 "{'from': 1, 'date': 1615480369, 'message': 'Hello', 'roomId': '1:2'}"`.
41 |
42 | We use a stringified _JSON_ for keeping the message structure and simplify the implementation details for this demo-app.
43 |
44 | **Populate the "General" room with messages.** Messages are added to the sorted set with id of the "General" room: `room:0`
45 |
46 | ### Registration
47 |
48 | 
49 |
50 | Redis is used mainly as a database to keep the user/messages data and for sending messages between connected servers.
51 |
52 | #### How the data is stored:
53 |
54 | - The chat data is stored in various keys and various data types.
55 | - User data is stored in a hash set where each user entry contains the next values:
56 | - `username`: unique user name;
57 | - `password`: hashed password
58 |
59 | * User hash set is accessed by key `user:{userId}`. The data for it stored with `HSET key field data`. User id is calculated by incrementing the `total_users`.
60 |
61 | - E.g `INCR total_users`
62 |
63 | * Username is stored as a separate key (`username:{username}`) which returns the userId for quicker access.
64 | - E.g `SET username:Alex 4`
65 |
66 | #### How the data is accessed:
67 |
68 | - **Get User** `HGETALL user:{id}`
69 |
70 | - E.g `HGETALL user:2`, where we get data for the user with id: 2.
71 |
72 | - **Online users:** will return ids of users which are online
73 | - E.g `SMEMBERS online_users`
74 |
75 | #### Code Example: Prepare User Data in Redis HashSet
76 |
77 | ```Python
78 | def create_user(username, password):
79 | username_key = make_username_key(username)
80 | # Create a user
81 | hashed_password = bcrypt.hashpw(str(password).encode("utf-8"), bcrypt.gensalt(10))
82 | next_id = redis_client.incr("total_users")
83 | user_key = f"user:{next_id}"
84 | redis_client.set(username_key, user_key)
85 | redis_client.hmset(user_key, {"username": username, "password": hashed_password})
86 |
87 | redis_client.sadd(f"user:{next_id}:rooms", "0")
88 |
89 | return {"id": next_id, "username": username}
90 | ```
91 |
92 | ### Rooms
93 |
94 | 
95 |
96 | #### How the data is stored:
97 |
98 | Each user has a set of rooms associated with them.
99 |
100 | **Rooms** are sorted sets which contains messages where score is the timestamp for each message. Each room has a name associated with it.
101 |
102 | - Rooms which user belongs too are stored at `user:{userId}:rooms` as a set of room ids.
103 |
104 | - E.g `SADD user:Alex:rooms 1`
105 |
106 | - Set room name: `SET room:{roomId}:name {name}`
107 | - E.g `SET room:1:name General`
108 |
109 | #### How the data is accessed:
110 |
111 | - **Get room name** `GET room:{roomId}:name`.
112 |
113 | - E. g `GET room:0:name`. This should return "General"
114 |
115 | - **Get room ids of a user:** `SMEMBERS user:{id}:rooms`.
116 | - E. g `SMEMBERS user:2:rooms`. This will return IDs of rooms for user with ID: 2
117 |
118 | #### Code Example: Get all My Rooms
119 |
120 | ```Python
121 | def get_rooms_for_user_id(user_id=0):
122 | """Get rooms for the selected user."""
123 | # We got the room ids
124 | room_ids = list(
125 | map(
126 | lambda x: x.decode("utf-8"),
127 | list(utils.redis_client.smembers(f"user:{user_id}:rooms")),
128 | )
129 | )
130 | rooms = []
131 |
132 | for room_id in room_ids:
133 | name = utils.redis_client.get(f"room:{room_id}:name")
134 |
135 | # It's a room without a name, likey the one with private messages
136 | if not name:
137 | room_exists = utils.redis_client.exists(f"room:{room_id}")
138 | if not room_exists:
139 | continue
140 |
141 | user_ids = room_id.split(":")
142 | if len(user_ids) != 2:
143 | return jsonify(None), 400
144 |
145 | rooms.append(
146 | {
147 | "id": room_id,
148 | "names": [
149 | utils.hmget(f"user:{user_ids[0]}", "username"),
150 | utils.hmget(f"user:{user_ids[1]}", "username"),
151 | ],
152 | }
153 | )
154 | else:
155 | rooms.append({"id": room_id, "names": [name.decode("utf-8")]})
156 | return jsonify(rooms), 200
157 | ```
158 |
159 | ### Messages
160 |
161 | #### Pub/sub
162 |
163 | After initialization, a pub/sub subscription is created: `SUBSCRIBE MESSAGES`. At the same time, each server instance will run a listener on a message on this channel to receive real-time updates.
164 |
165 | Again, for simplicity, each message is serialized to **_JSON_**, which we parse and then handle in the same manner, as WebSocket messages.
166 |
167 | Pub/sub allows connecting multiple servers written in different platforms without taking into consideration the implementation detail of each server.
168 |
169 | #### How the data is stored:
170 |
171 | - Messages are stored at `room:{roomId}` key in a sorted set (as mentioned above). They are added with `ZADD room:{roomId} {timestamp} {message}` command. Message is serialized to an app-specific JSON string.
172 | - E.g `ZADD room:0 1617197047 { "From": "2", "Date": 1617197047, "Message": "Hello", "RoomId": "1:2" }`
173 |
174 | #### How the data is accessed:
175 |
176 | - **Get list of messages** `ZREVRANGE room:{roomId} {offset_start} {offset_end}`.
177 | - E.g `ZREVRANGE room:1:2 0 50` will return 50 messages with 0 offsets for the private room between users with IDs 1 and 2.
178 |
179 | #### Code Example: Send Message
180 |
181 | ```Python
182 | # The user might be set as offline if he tried to access the chat from another tab, pinging by message
183 | # resets the user online status
184 | utils.redis_client.sadd("online_users", message["from"])
185 | # We've got a new message. Store it in db, then send back to the room. */
186 | message_string = json.dumps(message)
187 | room_id = message["roomId"]
188 | room_key = f"room:{room_id}"
189 |
190 | is_private = not bool(utils.redis_client.exists(f"{room_key}:name"))
191 | room_has_messages = bool(utils.redis_client.exists(room_key))
192 |
193 | if is_private and not room_has_messages:
194 | ids = room_id.split(":")
195 | msg = {
196 | "id": room_id,
197 | "names": [
198 | utils.hmget(f"user:{ids[0]}", "username"),
199 | utils.hmget(f"user:{ids[1]}", "username"),
200 | ],
201 | }
202 | publish("show.room", msg, broadcast=True)
203 | utils.redis_client.zadd(room_key, {message_string: int(message["date"])})
204 | ```
205 |
206 | ### Session handling
207 |
208 | The chat server works as a basic _REST_ API which involves keeping the session and handling the user state in the chat rooms (besides the WebSocket/real-time part).
209 |
210 | When a WebSocket/real-time server is instantiated, which listens for the next events:
211 |
212 | **Connection**. A new user is connected. At this point, a user ID is captured and saved to the session (which is cached in Redis). Note, that session caching is language/library-specific and it's used here purely for persistence and maintaining the state between server reloads.
213 |
214 | A global set with `online_users` key is used for keeping the online state for each user. So on a new connection, a user ID is written to that set:
215 |
216 | **E.g.** `SADD online_users 1` (We add user with id 1 to the set **online_users**).
217 |
218 | After that, a message is broadcasted to the clients to notify them that a new user is joined the chat.
219 |
220 | **Disconnect**. It works similarly to the connection event, except we need to remove the user for **online_users** set and notify the clients: `SREM online_users 1` (makes user with id 1 offline).
221 |
222 | **Message**. A user sends a message, and it needs to be broadcasted to the other clients. The pub/sub allows us also to broadcast this message to all server instances which are connected to this Redis:
223 |
224 | `PUBLISH message "{'serverId': 4132, 'type':'message', 'data': {'from': 1, 'date': 1615480369, 'message': 'Hello', 'roomId': '1:2'}}"`
225 |
226 | Note we send additional data related to the type of the message and the server id. Server id is used to discard the messages by the server instance which sends them since it is connected to the same `MESSAGES` channel.
227 |
228 | `type` field of the serialized JSON corresponds to the real-time method we use for real-time communication (connect/disconnect/message).
229 |
230 | `data` is method-specific information. In the example above it's related to the new message.
231 |
232 | #### How the data is stored / accessed:
233 |
234 | The session data is stored in Redis by utilizing the [**redis**](https://pypi.org/project/redis/) client module.
235 |
236 | ```Python
237 | class Config(object):
238 | # Parse redis environment variables.
239 | redis_endpoint_url = os.environ.get("REDIS_ENDPOINT_URL", "127.0.0.1:6379")
240 | REDIS_HOST, REDIS_PORT = tuple(redis_endpoint_url.split(":"))
241 | REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", None)
242 | SECRET_KEY = os.environ.get("SECRET_KEY", "Optional default value")
243 | SESSION_TYPE = "redis"
244 | redis_client = redis.Redis(
245 | host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD
246 | )
247 | SESSION_REDIS = redis_client
248 | ```
249 |
250 | ```Python
251 | from flask_session import Session
252 | sess = Session()
253 | # ...
254 | sess.init_app(app)
255 | ```
256 |
257 | ## How to run it locally?
258 |
259 | #### Copy `.env.sample` to create `.env`. And provide the values for environment variables
260 |
261 | - REDIS_ENDPOINT_URI: Redis server URI
262 | - REDIS_PASSWORD: Password to the server
263 |
264 | #### Run frontend
265 |
266 | ```sh
267 | cd client
268 | yarn install
269 | yarn start
270 | ```
271 |
272 | #### Run backend
273 |
274 | Run with venv:
275 |
276 | ```sh
277 | python app.py
278 | ```
279 |
280 | ## Try it out
281 |
282 | #### Deploy to Heroku
283 |
284 |
285 |
286 |
287 |
288 |
293 |
294 |
295 |
296 |