Merge pull request #377 from ricardoapaes/feature/docker-for-production-v2

Enhancing docker for production
This commit is contained in:
Cassio Santos
2022-02-17 22:00:49 -03:00
committed by GitHub
32 changed files with 328 additions and 86 deletions

View File

@@ -1,7 +1,24 @@
MYSQL_ROOT_PASSWORD=strongpassword
# MYSQL
MYSQL_ENGINE=
MYSQL_VERSION=
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=whaticket
JWT_SECRET=3123123213123
JWT_REFRESH_SECRET=75756756756
FRONTEND_PORT=3000
TZ=America/Fortaleza
MAX_CONCURRENT_SESSIONS=1
MYSQL_PORT=
TZ=
# BACKEND
BACKEND_PORT=
BACKEND_SERVER_NAME=api.mydomain.com
BACKEND_URL=https://api.mydomain.com
PROXY_PORT=443
JWT_SECRET=
JWT_REFRESH_SECRET=
# FRONTEND
FRONTEND_PORT=80
FRONTEND_SSL_PORT=443
FRONTEND_SERVER_NAME=myapp.mydomain.com
FRONTEND_URL=https://myapp.mydomain.com
# BROWSERLESS
MAX_CONCURRENT_SESSIONS=

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.docker/data/
ssl/
.env

View File

@@ -45,6 +45,10 @@ _Note_: change MYSQL_DATABASE, MYSQL_PASSWORD, MYSQL_USER and MYSQL_ROOT_PASSWOR
```bash
docker run --name whaticketdb -e MYSQL_ROOT_PASSWORD=strongpassword -e MYSQL_DATABASE=whaticket -e MYSQL_USER=whaticket -e MYSQL_PASSWORD=whaticket --restart always -p 3306:3306 -d mariadb:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
# Or run using `docker-compose` as below
# Before copy .env.example to .env first and set the variables in the file.
docker-compose up -d mysql
```
Install puppeteer dependencies:
@@ -119,7 +123,9 @@ npm start
- Wait for QR CODE button to appear, click it and read qr code.
- Done. Every message received by your synced WhatsApp number will appear in Tickets List.
## Basic production deployment (Ubuntu 20.04 VPS)
## Basic production deployment
### Using Ubuntu 20.04 VPS
All instructions below assumes you are NOT running as root, since it will give an error in puppeteer. So let's start creating a new user and granting sudo privileges to it:
@@ -169,6 +175,10 @@ _Note_: change MYSQL_DATABASE, MYSQL_PASSWORD, MYSQL_USER and MYSQL_ROOT_PASSWOR
```bash
docker run --name whaticketdb -e MYSQL_ROOT_PASSWORD=strongpassword -e MYSQL_DATABASE=whaticket -e MYSQL_USER=whaticket -e MYSQL_PASSWORD=whaticket --restart always -p 3306:3306 -d mariadb:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
# Or run using `docker-compose` as below
# Before copy .env.example to .env first and set the variables in the file.
docker-compose up -d mysql
```
Clone this repository:
@@ -373,6 +383,83 @@ Enable SSL on nginx (Fill / Accept all information required):
sudo certbot --nginx
```
### Using docker and docker-compose
To run WhaTicket using docker you must perform the following steps:
```bash
cp .env.example .env
```
Now it will be necessary to configure the .env using its information, the variables are the same as those mentioned in the deployment using ubuntu, with the exception of mysql settings that were not in the .env.
```bash
# MYSQL
MYSQL_ENGINE= # default: mariadb
MYSQL_VERSION= # default: 10.6
MYSQL_ROOT_PASSWORD=strongpassword # change it please
MYSQL_DATABASE=whaticket
MYSQL_PORT=3306 # default: 3306; Use this port to expose mysql server
TZ=America/Fortaleza # default: America/Fortaleza; Timezone for mysql
# BACKEND
BACKEND_PORT= # default: 8080; but access by host not use this port
BACKEND_SERVER_NAME=api.mydomain.com
BACKEND_URL=https://api.mydomain.com
PROXY_PORT=443
JWT_SECRET=3123123213123 # change it please
JWT_REFRESH_SECRET=75756756756 # change it please
# FRONTEND
FRONTEND_PORT=80 # default: 3000; Use port 80 to expose in production
FRONTEND_SSL_PORT=443 # default: 3001; Use port 443 to expose in production
FRONTEND_SERVER_NAME=myapp.mydomain.com
FRONTEND_URL=https://myapp.mydomain.com
# BROWSERLESS
MAX_CONCURRENT_SESSIONS= # default: 1; Use only if using browserless
```
After defining the variables, run the following command:
```bash
docker-compose up -d --build
```
On the `first` run it will be necessary to seed the database tables using the following command:
```bash
docker-compose exec backend npx sequelize db:seed:all
```
#### SSL Certificate
To deploy the ssl certificate, add it to the `ssl/certs` folder. Inside it there should be a `backend` and a `frontend` folder, and each of them should contain the files `fullchain.pem` and `privkey.pem`, as in the structure below:
```bash
.
├── certs
│   ├── backend
│   │   ├── fullchain.pem
│   │   └── privkey.pem
│   └── frontend
│   ├── fullchain.pem
│   └── privkey.pem
└── www
```
To generate the certificate files use `certbot` which can be installed using snap, I used the following command:
Note: The frontend container that runs nginx is already prepared to receive the request made by certboot to validate the certificate.
```bash
# FRONTEND
certbot certonly --cert-name backend --webroot --webroot-path ./ssl/www/ -d api.mydomain.com
# BACKEND
certbot certonly --cert-name frontend --webroot --webroot-path ./ssl/www/ -d myapp.mydomain.com
```
## Access Data
User: admin@whaticket.com

View File

@@ -16,25 +16,35 @@ services:
- DB_NAME=${MYSQL_DATABASE:-whaticket}
- JWT_SECRET=${JWT_SECRET:-3123123213123}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-75756756756}
- BACKEND_URL=${BACKEND_URL:-http://localhost:3000/api}
- FRONTEND_URL=${BACKEND_URL:-http://localhost:3000}
- BACKEND_URL=${BACKEND_URL:-http://localhost}
- FRONTEND_URL=${FRONTEND_URL:-http://localhost:3000}
- PROXY_PORT=${PROXY_PORT:-8080}
- CHROME_ARGS=--no-sandbox --disable-setuid-sandbox
ports:
- ${BACKEND_PORT:-8080}:3000
networks:
- whaticket
frontend:
ports:
- ${FRONTEND_PORT:-3000}:80
- ${FRONTEND_SSL_PORT:-3001}:443
build:
context: ./frontend
dockerfile: ./Dockerfile
environment:
- URL_BACKEND=http://backend:3000/
- URL_BACKEND=backend:3000
- REACT_APP_BACKEND_URL=${BACKEND_URL:-http://localhost}:${PROXY_PORT:-8080}/
- FRONTEND_SERVER_NAME=${FRONTEND_SERVER_NAME}
- BACKEND_SERVER_NAME=${BACKEND_SERVER_NAME}
volumes:
- ./ssl/certs/:/etc/nginx/ssl/
- ./ssl/www/:/var/www/letsencrypt/
networks:
- whaticket
mysql:
image: mariadb:latest
image: ${MYSQL_ENGINE:-mariadb}:${MYSQL_VERSION:-10.6}
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
volumes:
- ./.docker/data/:/var/lib/mysql
@@ -43,7 +53,7 @@ services:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-strongpassword}
- TZ=${TZ:-America/Fortaleza}
ports:
- 3306:3306
- ${MYSQL_PORT:-3306}:3306
restart: always
networks:
- whaticket

View File

@@ -0,0 +1,30 @@
_writeFrontendEnvVars() {
ENV_JSON="$(jq --compact-output --null-input 'env | with_entries(select(.key | startswith("REACT_APP_")))')"
ENV_JSON_ESCAPED="$(printf "%s" "${ENV_JSON}" | sed -e 's/[\&/]/\\&/g')"
sed -i "s/<noscript id=\"env-insertion-point\"><\/noscript>/<script>var ENV=${ENV_JSON_ESCAPED}<\/script>/g" ${PUBLIC_HTML}index.html
}
_writeNginxEnvVars() {
dockerize -template /etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
}
_addSslConfig() {
SSL_CERTIFICATE=/etc/nginx/ssl/${1}/fullchain.pem;
SSL_CERTIFICATE_KEY=/etc/nginx/ssl/${1}/privkey.pem;
FILE_CONF=/etc/nginx/sites.d/${1}.conf
if [ -f ${SSL_CERTIFICATE} ] && [ -f ${SSL_CERTIFICATE_KEY} ]; then
echo "saving ssl config in ${FILE_CONF}"
echo 'include "include.d/ssl.conf";' >> ${FILE_CONF};
echo "ssl_certificate ${SSL_CERTIFICATE};" >> ${FILE_CONF};
echo "ssl_certificate_key ${SSL_CERTIFICATE_KEY};" >> ${FILE_CONF};
else
echo "ssl ${1} not found >> ${SSL_CERTIFICATE} -> ${SSL_CERTIFICATE_KEY}"
fi;
}
_writeFrontendEnvVars;
_writeNginxEnvVars;
_addSslConfig 'backend'
_addSslConfig 'frontend'

View File

@@ -1,4 +1,27 @@
server {
server_name _;
include include.d/spa.conf;
include include.d/ssl-redirect.conf;
upstream backend {
server {{ .Env.URL_BACKEND }};
}
server {
index index.html;
root /var/www/public/;
{{ if .Env.FRONTEND_SERVER_NAME }}
server_name {{ .Env.FRONTEND_SERVER_NAME }};
{{else}}
server_name _;
{{end}}
include sites.d/frontend.conf;
include include.d/letsencrypt.conf;
}
{{if .Env.BACKEND_SERVER_NAME}}
server {
server_name {{ .Env.BACKEND_SERVER_NAME }};
include sites.d/backend.conf;
include include.d/letsencrypt.conf;
}
{{end}}

View File

@@ -0,0 +1,45 @@
#############################################################################
# Configuration file for Let's Encrypt ACME Challenge location
# This file is already included in listen_xxx.conf files.
# Do NOT include it separately!
#############################################################################
#
# This config enables to access /.well-known/acme-challenge/xxxxxxxxxxx
# on all our sites (HTTP), including all subdomains.
# This is required by ACME Challenge (webroot authentication).
# You can check that this location is working by placing ping.txt here:
# /var/www/letsencrypt/.well-known/acme-challenge/ping.txt
# And pointing your browser to:
# http://xxx.domain.tld/.well-known/acme-challenge/ping.txt
#
# Sources:
# https://community.letsencrypt.org/t/howto-easy-cert-generation-and-renewal-with-nginx/3491
#
#############################################################################
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";
# This directory must be the same as in /etc/letsencrypt/cli.ini
# as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
# there to "webroot".
# Do NOT use alias, use root! Target directory is located here:
# /var/www/common/letsencrypt/.well-known/acme-challenge/
root /var/www/letsencrypt;
autoindex on;
}
# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
return 404;
}

View File

@@ -1,16 +0,0 @@
# X-Frame-Options is to prevent from clickJacking attack
add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
add_header Referrer-Policy "no-referrer-when-downgrade";
# Enables response header of "Vary: Accept-Encoding"
gzip_vary on;

View File

@@ -1,28 +1,16 @@
listen 80;
index index.html;
root /var/www/public/;
# X-Frame-Options is to prevent from clickJacking attack
add_header X-Frame-Options SAMEORIGIN;
location /{{ default .Env.FOLDER_BACKEND "api" }}/ {
proxy_pass {{ .Env.URL_BACKEND }};
}
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
location /socket.io/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass {{ .Env.URL_BACKEND }}socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
location / {
try_files $uri $uri/ /index.html;
include include.d/nocache.conf;
}
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
location /static {
alias /var/www/public/static/;
include include.d/allcache.conf;
}
add_header Referrer-Policy "no-referrer-when-downgrade";
include include.d/root.conf;
# Enables response header of "Vary: Accept-Encoding"
gzip_vary on;

View File

@@ -0,0 +1,5 @@
server {
listen 80;
listen [::]:80;
return 302 https://$host$request_uri;
}

View File

@@ -0,0 +1,2 @@
listen 443 ssl http2;
listen [::]:443 ssl http2;

View File

@@ -0,0 +1,11 @@
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}

View File

@@ -0,0 +1,11 @@
location / {
try_files $uri $uri/ /index.html;
include include.d/nocache.conf;
}
location /static {
alias /var/www/public/static/;
include include.d/allcache.conf;
}
include "include.d/spa.conf";

View File

@@ -1,24 +1,25 @@
FROM node:14-alpine as build-deps
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
COPY package*.json ./
RUN npm install
COPY .env* ./
COPY src/ ./src/
COPY public/ ./public/
RUN echo "REACT_APP_BACKEND_URL=/api/" > .env.production
RUN npm run build
FROM nginx:alpine
RUN apk add --no-cache jq openssl
RUN apk add --no-cache openssl
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz
ENV PUBLIC_HTML=/var/www/public/
COPY .docker/nginx /etc/nginx/
COPY --from=build-deps /usr/src/app/build /var/www/public/
COPY --from=build-deps /usr/src/app/build ${PUBLIC_HTML}
EXPOSE 80
RUN echo "dockerize -template /etc/nginx/include.d/spa.conf:/etc/nginx/include.d/spa.conf" > /docker-entrypoint.d/01-change-url-backend.sh \
&& chmod +x /docker-entrypoint.d/01-change-url-backend.sh
COPY .docker/add-env-vars.sh /docker-entrypoint.d/01-add-env-vars.sh
RUN chmod +x /docker-entrypoint.d/01-add-env-vars.sh

View File

@@ -15,6 +15,7 @@
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
<noscript id="env-insertion-point"></noscript>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useReducer, useRef } from "react";
import { isSameDay, parseISO, format } from "date-fns";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import clsx from "clsx";
import { green } from "@material-ui/core/colors";
@@ -358,7 +358,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
}, [pageNumber, ticketId]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("connect", () => socket.emit("joinChatBox", ticketId));

View File

@@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect, useContext } from "react";
import { useHistory } from "react-router-dom";
import { format } from "date-fns";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import useSound from "use-sound";
import Popover from "@material-ui/core/Popover";
@@ -78,7 +78,7 @@ const NotificationsPopOver = () => {
}, [ticketIdUrl]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("connect", () => socket.emit("joinNotification"));

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import QRCode from "qrcode.react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import toastError from "../../errors/toastError";
import { Dialog, DialogContent, Paper, Typography } from "@material-ui/core";
@@ -26,7 +26,7 @@ const QrcodeModal = ({ open, onClose, whatsAppId }) => {
useEffect(() => {
if (!whatsAppId) return;
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("whatsappSession", data => {
if (data.action === "update" && data.session.id === whatsAppId) {

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { useParams, useHistory } from "react-router-dom";
import { toast } from "react-toastify";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import clsx from "clsx";
import { Paper, makeStyles } from "@material-ui/core";
@@ -104,7 +104,7 @@ const Ticket = () => {
}, [ticketId, history]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("connect", () => socket.emit("joinChatBox", ticketId));

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer, useContext } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles";
import List from "@material-ui/core/List";
@@ -182,7 +182,7 @@ const reducer = (state, action) => {
}, [tickets, status, searchParam]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
const shouldUpdateTicket = ticket =>
(!ticket.userId || ticket.userId === user?.id || showAll) &&

16
frontend/src/config.js Normal file
View File

@@ -0,0 +1,16 @@
function getConfig(name, defaultValue=null) {
// If inside a docker container, use window.ENV
if( window.ENV !== undefined ) {
return window.ENV[name] || defaultValue;
}
return process.env[name] || defaultValue;
}
export function getBackendUrl() {
return getConfig('REACT_APP_BACKEND_URL');
}
export function getHoursCloseTicketsAuto() {
return getConfig('REACT_APP_HOURS_CLOSE_TICKETS_AUTO');
}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import { toast } from "react-toastify";
@@ -71,7 +71,7 @@ const useAuth = () => {
}, []);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("user", data => {
if (data.action === "update" && data.user.id === user.id) {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from "react";
import { getHoursCloseTicketsAuto } from "../../config";
import toastError from "../../errors/toastError";
import api from "../../services/api";
@@ -35,7 +36,7 @@ const useTickets = ({
})
setTickets(data.tickets)
let horasFecharAutomaticamente = process.env.REACT_APP_HOURS_CLOSE_TICKETS_AUTO
let horasFecharAutomaticamente = getHoursCloseTicketsAuto();
if (status === "open" && horasFecharAutomaticamente && horasFecharAutomaticamente !== "" &&
horasFecharAutomaticamente !== "0" && Number(horasFecharAutomaticamente) > 0) {

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import toastError from "../../errors/toastError";
import api from "../../services/api";
@@ -73,7 +73,7 @@ const useWhatsApps = () => {
}, []);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("whatsapp", data => {
if (data.action === "update") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer, useContext } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import { toast } from "react-toastify";
import { useHistory } from "react-router-dom";
@@ -130,7 +130,7 @@ const Contacts = () => {
}, [searchParam, pageNumber]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("contact", (data) => {
if (data.action === "update" || data.action === "create") {

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useReducer, useState } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import {
Button,
@@ -111,7 +111,7 @@ const Queues = () => {
}, []);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("queue", (data) => {
if (data.action === "update" || data.action === "create") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import {
Button,
@@ -122,7 +122,7 @@ const QuickAnswers = () => {
}, [searchParam, pageNumber]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("quickAnswer", (data) => {
if (data.action === "update" || data.action === "create") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper";
@@ -55,7 +55,7 @@ const Settings = () => {
}, []);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("settings", data => {
if (data.action === "update") {

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useReducer } from "react";
import { toast } from "react-toastify";
import openSocket from "socket.io-client";
import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper";
@@ -122,7 +122,7 @@ const Users = () => {
}, [searchParam, pageNumber]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const socket = openSocket();
socket.on("user", (data) => {
if (data.action === "update" || data.action === "create") {

View File

@@ -1,7 +1,8 @@
import axios from "axios";
import { getBackendUrl } from "../config";
const api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
baseURL: getBackendUrl(),
withCredentials: true,
});

View File

@@ -0,0 +1,8 @@
import openSocket from "socket.io-client";
import { getBackendUrl } from "../config";
function connectToSocket() {
return openSocket(getBackendUrl());
}
export default connectToSocket;