From 98990809598896f015db311a0816ca00432ba590 Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 03:37:19 +0300 Subject: [PATCH 1/7] install ws --- client/package-lock.json | 5 +++++ client/package.json | 1 + package.json | 1 + server/package-lock.json | 5 +++++ server/package.json | 5 +++-- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 37f90c3..1ab4509 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -13066,6 +13066,11 @@ "warning": "^3.0.0" } }, + "react-use-websocket": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-2.7.1.tgz", + "integrity": "sha512-n0H429jQGaZAnn8BJljCcAgeSezXYzTLO0tb5QvdZnNjhAA8Br3A3GjU5ziNMlxant++Iv/IRQRNMKCfWnsHrQ==" + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", diff --git a/client/package.json b/client/package.json index 13395a9..d340ae3 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "react-scripts": "4.0.3", "react-stack-grid": "^0.7.1", "react-textarea-autosize": "^8.3.3", + "react-use-websocket": "^2.7.1", "remark-gfm": "^1.0.0", "web-vitals": "^0.2.4", "workbox-background-sync": "^5.1.4", diff --git a/package.json b/package.json index 7f61485..8109031 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dev": "cross-env NODE_ENV=dev concurrently \"npm run server:start-dev\" \"npm run client:start-dev\"", "prod": "npm run server:start-prod", "prod:predeploy": "npm run deploy && npm run prod", + "prod:prebuild": "npm run client:build && npm run prod", "test": "echo \"Error: no test specified\" && exit 1", "heroku-postbuild": "npm run deploy", "mongo-ubuntu:update": "(sudo apt update && sudo apt upgrade)", diff --git a/server/package-lock.json b/server/package-lock.json index 9fba14c..77cf659 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1669,6 +1669,11 @@ "typedarray-to-buffer": "^3.1.5" } }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/server/package.json b/server/package.json index fa21c55..f167922 100644 --- a/server/package.json +++ b/server/package.json @@ -16,9 +16,10 @@ "express": "^4.17.1", "express-validator": "^6.11.1", "jsonwebtoken": "^8.5.1", - "mongoose": "^5.12.13" + "mongoose": "^5.12.13", + "ws": "^7.4.6" }, "devDependencies": { "nodemon": "^2.0.7" } -} \ No newline at end of file +} From 0eb0f59d55a076e7aeb2a9d5ac084a6d7be0f4fa Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 03:38:26 +0300 Subject: [PATCH 2/7] use debounced function --- client/src/Hooks/useDebouncedEffect.hook.js | 37 ------------------- client/src/Hooks/useDebouncedFunction.hook.js | 21 +++++++++++ 2 files changed, 21 insertions(+), 37 deletions(-) delete mode 100644 client/src/Hooks/useDebouncedEffect.hook.js create mode 100644 client/src/Hooks/useDebouncedFunction.hook.js diff --git a/client/src/Hooks/useDebouncedEffect.hook.js b/client/src/Hooks/useDebouncedEffect.hook.js deleted file mode 100644 index 1d61219..0000000 --- a/client/src/Hooks/useDebouncedEffect.hook.js +++ /dev/null @@ -1,37 +0,0 @@ -/** @file useDebouncedEffect.hook.js */ -import { useState, useEffect } from 'react'; - -/** - * Хук debounce - * @param {*} value - * @param {*} delay - */ -function useDebounce(value, delay) { - const [debouncedValue, setDebouncedValue] = useState(value); - useEffect( - () => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - return () => { - clearTimeout(handler); - }; - }, - [delay, value] - ); - return debouncedValue; -} - -/** - * Хук debounced effect - * @param {*} func - * @param {*} deps - * @param {*} delay - */ -function useDebouncedEffect(func, deps, delay) { - const debounced = useDebounce(deps, delay || 0) - const debDepsList = Array.isArray(debounced) ? debounced : debounced ? [debounced] : undefined - useEffect(func, debDepsList) // eslint-disable-line react-hooks/exhaustive-deps -} - -export default useDebouncedEffect \ No newline at end of file diff --git a/client/src/Hooks/useDebouncedFunction.hook.js b/client/src/Hooks/useDebouncedFunction.hook.js new file mode 100644 index 0000000..f9a71c6 --- /dev/null +++ b/client/src/Hooks/useDebouncedFunction.hook.js @@ -0,0 +1,21 @@ +/** @file useDebouncedFunction.hook.js */ +import { useEffect, useReducer } from 'react'; + +/** + * Хук Debounced Function + * @param {void} callback + * @param {number} delayms + */ +function useDebouncedFunction(callback, delayms) { + const [state, debounced] = useReducer(() => { + return { count: state.count++, args: arguments } + }, { count: 0, args: undefined }) + useEffect(() => { + const handler = setTimeout(() => callback(state.arguments), delayms) + return () => clearTimeout(handler) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]) + return debounced +} + +export default useDebouncedFunction \ No newline at end of file From 6c6abf49ee6d09a178351214bf9dc0cf77e25e5e Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 03:38:57 +0300 Subject: [PATCH 3/7] create updater socket --- client/src/Hooks/useUpdaterSocket.hook.js | 69 ++++++++++++++++++++++ client/src/Pages/NotesPage.js | 6 +- jsdoc.json | 1 + server/app.js | 35 +++++++---- server/socket/wss.js | 71 +++++++++++++++++++++++ 5 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 client/src/Hooks/useUpdaterSocket.hook.js create mode 100644 server/socket/wss.js diff --git a/client/src/Hooks/useUpdaterSocket.hook.js b/client/src/Hooks/useUpdaterSocket.hook.js new file mode 100644 index 0000000..4c31a7f --- /dev/null +++ b/client/src/Hooks/useUpdaterSocket.hook.js @@ -0,0 +1,69 @@ +/**@file useUpdaterSocket.hook.js */ +import { useCallback } from "react" +import useWebSocket from 'react-use-websocket' +import useDebouncedFunction from "./useDebouncedFunction.hook" +import { useHttp } from "./http.hook" + +const WS_PORT = process.env.WS_PORT || 3030 + +/** + * Хук веб-сокета обновления данных + * @param {void} updateData + * @param {*} auth + */ +function useUpdaterSocket(updateData, auth) { + const { request } = useHttp() + const getSocketUrl = useCallback(async () => { + return new Promise(resolve => { + request("/getIp") + .then((data) => { + const ip = data.ip + const socketAddress = "ws://" + (ip || "localhost") + ":" + WS_PORT + console.log("socketAddress", socketAddress) + resolve(socketAddress) + }) + .catch((err) => console.log(err)) + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + const debouncedUpdate = useDebouncedFunction(updateData, 200) + + const socketOptions = { + onOpen: (e) => { + sendRegisterMsg() + console.log("ws open", e) + }, + onClose: (e) => { + console.log("ws close", e) + }, + onMessage: (e) => { + console.log("ws updMessage", e) + debouncedUpdate() + }, + onError: (e) => { + console.error("ws error", e) + }, + } + + const { sendMessage } = useWebSocket(getSocketUrl, socketOptions) + + const sendMsg = (target) => { + const msg = JSON.stringify({ + userId: auth.userId, + target + }) + sendMessage(msg) + } + + function sendUpdateMsg() { + sendMsg("update") + } + + function sendRegisterMsg() { + sendMsg("register") + } + + return [sendUpdateMsg] +} + +export default useUpdaterSocket \ No newline at end of file diff --git a/client/src/Pages/NotesPage.js b/client/src/Pages/NotesPage.js index 5ef4b04..d37651b 100644 --- a/client/src/Pages/NotesPage.js +++ b/client/src/Pages/NotesPage.js @@ -17,6 +17,7 @@ import useFetchNotes from '../Hooks/useFetchNotes.hook' import useEditNoteId from '../Hooks/useEditNoteId.hook' import useNotesArr from '../Hooks/useNotesArr.hook' import { calcOrder, fixOrders } from '../Shared/order' +import useUpdaterSocket from '../Hooks/useUpdaterSocket.hook' /** * Страница с заметками @@ -52,6 +53,9 @@ function NotesPage() { /** подключение контроллера обновления данных */ const [updateData] = useDataLoadingController(loadDataFromServer, setLoadedNotes, auth, 60) + /** подключение сокета обновления данных */ + const [sendUpdateMsg] = useUpdaterSocket(updateData, auth) + /////////// /** @@ -67,7 +71,7 @@ function NotesPage() { * @param {string} target */ function loadDataToServer(note = new Note(), target = 'set') { - fetchNotes(target, "POST", { note }) + fetchNotes(target, "POST", { note }, sendUpdateMsg) } /////////// diff --git a/jsdoc.json b/jsdoc.json index ace4c21..65af500 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -6,6 +6,7 @@ "server/routes", "server/models", "server/middleware", + "server/socket", "client", "client/src", "client/src/Shared", diff --git a/server/app.js b/server/app.js index c369222..7241508 100644 --- a/server/app.js +++ b/server/app.js @@ -5,30 +5,35 @@ const express = require('express') const path = require('path') -const dns = require('dns') const os = require('os') const mongoose = require('mongoose') const https = require('./middleware/https.middleware') +const startWSS = require('./socket/wss') require('dotenv').config() -/** - * подключение переменных среды - */ +//подключение переменных среды const devMode = process.env.NODE_ENV === "dev" const PORT = process.env.PORT || 5000 const mongoUri = process.env.mongoUri const httpsRedirect = process.env.httpsRedirect || false +const WS_PORT = process.env.WS_PORT || 3030 const app = express() app.use(express.json({ extended: true })) +app.get('/getIp', (req, res) => { + const ip = getIp() + res.status(200).json({ ip }) +}) + /** * подключение роутов */ app.use('/api/auth', require('./routes/auth.routes')) app.use('/api/notes', require('./routes/notes.routes')) + if (httpsRedirect) app.use(https) /** @@ -52,6 +57,7 @@ if (!devMode) { async function start() { try { connectMongo(mongoUri) + startWSS(WS_PORT) app.listen(PORT, logServerStart) } catch (e) { console.log('Server Error', e.message) @@ -81,11 +87,18 @@ async function connectMongo(mongoUri) { * Вывод информации о сервере */ function logServerStart() { - dns.lookup(os.hostname(), (err, address) => { - const [logName, sBef, sAft] = devMode ? ['Express server', ' ', ':'] : ['React Notes App', '-', ''] - console.log(`\n${logName} has been started`) - console.log(`${sBef} Local${sAft} http://localhost:${PORT}`) - console.log(`${sBef} On Your Network${sAft} http://${address}:${PORT}`) - if (err) console.log(err) - }) + const [logName, sBef, sAft] = devMode ? ['Express server', ' ', ':'] : ['React Notes App', '-', ''] + console.log(`\n${logName} has been started`) + console.log(`${sBef} Local${sAft} http://localhost:${PORT}`) + console.log(`${sBef} On Your Network${sAft} http://${getIp()}:${PORT}`, '\n') +} + +/** + * Получение ip сервера + */ +function getIp() { + for (let key in os.networkInterfaces()) { + const addr = os.networkInterfaces()[key][1].address + if (addr != undefined) return addr + } } diff --git a/server/socket/wss.js b/server/socket/wss.js new file mode 100644 index 0000000..5fe4464 --- /dev/null +++ b/server/socket/wss.js @@ -0,0 +1,71 @@ +/**@file wss.js */ +const WebSocket = require('ws') + +/** + * Подключение WebSocket сервера + * @param {*} port + */ +function startWSS(port) { + let wsCount = 0 + const wsServer = new WebSocket.Server({ port }) + const wsCollection = { test: [] } + wsServer.on('connection', (wsClient) => { + wsClient.num = ++wsCount + wsClient.on("message", (data) => { + try { + const { userId, target } = tryParce(data) + + if (target == "register") { + wsClient.userId = userId + let clients = getClients(userId) + clients.push({ + num: wsClient.num, + send: wsClient.send + }) + wsCollection[userId] = clients + + console.log("\ncollection \n", wsCollection, '\n') + } + + if (target == "update") { + getClients(wsClient.userId).forEach((wsc) => { + if ((wsClient.num === undefined) || (wsClient.num !== wsc.num)) wsc.send(data) + }) + } + + console.log("wsClient", wsClient.num, "messaged", target) + } catch (e) { + console.log("wsClient", wsClient.num, "error on messsage", e) + } + }) + wsClient.on("open", () => console.log("wsClient", wsClient.num, "opened")) + wsClient.on("close", () => { + wsCollection[wsClient.userId] = getClients(wsClient.userId).filter((wsc) => { + return (wsClient.num === undefined) || (wsClient.num !== wsc.num) + }) + console.log("wsClient", wsClient.num, "closed") + }) + wsClient.on("error", () => console.log("wsClient", wsClient.num, "error")) + + console.log("WSS connection", wsClient.num) + }); + wsServer.on("close", () => console.log("WSS closed")) + wsServer.on("error", () => console.log("WSS error")) + wsServer.on("listening", () => console.log("WSS listening")) + + /** + * Получение колекции соединений по id + * @param {string} userId + * @returns {Array} + */ + function getClients(userId) { + return wsCollection[userId] || [] + } + + function tryParce(str) { + try { return JSON.parse(str) } + catch (e) { return str } + } +} + +module.exports = startWSS \ No newline at end of file From f3d71ea4563a47a121f175140148a1d0a6f1b5f9 Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 04:35:26 +0300 Subject: [PATCH 4/7] debounce hook fixup --- client/src/Hooks/useDebouncedFunction.hook.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/Hooks/useDebouncedFunction.hook.js b/client/src/Hooks/useDebouncedFunction.hook.js index f9a71c6..9722687 100644 --- a/client/src/Hooks/useDebouncedFunction.hook.js +++ b/client/src/Hooks/useDebouncedFunction.hook.js @@ -3,15 +3,15 @@ import { useEffect, useReducer } from 'react'; /** * Хук Debounced Function - * @param {void} callback + * @param {(arg)=>void} callback * @param {number} delayms */ function useDebouncedFunction(callback, delayms) { - const [state, debounced] = useReducer(() => { - return { count: state.count++, args: arguments } - }, { count: 0, args: undefined }) + const [state, debounced] = useReducer((state, arg) => { + return { count: state.count++, arg: arg } + }, { count: 0, arg: undefined }) useEffect(() => { - const handler = setTimeout(() => callback(state.arguments), delayms) + const handler = setTimeout(() => callback(state.arg), delayms) return () => clearTimeout(handler) // eslint-disable-next-line react-hooks/exhaustive-deps }, [state]) From 4c7d78704e49b467c1bdef605c0897cebab723c8 Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 05:14:27 +0300 Subject: [PATCH 5/7] socket fixup --- client/src/Hooks/useUpdaterSocket.hook.js | 11 +++++++---- server/app.js | 20 +++++++++++-------- server/socket/wss.js | 24 +++++++++-------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/client/src/Hooks/useUpdaterSocket.hook.js b/client/src/Hooks/useUpdaterSocket.hook.js index 4c31a7f..28f4893 100644 --- a/client/src/Hooks/useUpdaterSocket.hook.js +++ b/client/src/Hooks/useUpdaterSocket.hook.js @@ -26,18 +26,19 @@ function useUpdaterSocket(updateData, auth) { }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + const debouncedUpdate = useDebouncedFunction(updateData, 200) const socketOptions = { onOpen: (e) => { sendRegisterMsg() - console.log("ws open", e) + console.log("ws open") }, onClose: (e) => { - console.log("ws close", e) + console.log("ws close") }, onMessage: (e) => { - console.log("ws updMessage", e) + console.log("ws update message") debouncedUpdate() }, onError: (e) => { @@ -55,6 +56,8 @@ function useUpdaterSocket(updateData, auth) { sendMessage(msg) } + const sendUpdateMsgDebounced = useDebouncedFunction(sendUpdateMsg, 200) + function sendUpdateMsg() { sendMsg("update") } @@ -63,7 +66,7 @@ function useUpdaterSocket(updateData, auth) { sendMsg("register") } - return [sendUpdateMsg] + return [sendUpdateMsgDebounced] } export default useUpdaterSocket \ No newline at end of file diff --git a/server/app.js b/server/app.js index 7241508..94b155e 100644 --- a/server/app.js +++ b/server/app.js @@ -23,8 +23,9 @@ const app = express() app.use(express.json({ extended: true })) app.get('/getIp', (req, res) => { - const ip = getIp() - res.status(200).json({ ip }) + getIp() + .then((ip) => res.status(200).json({ ip })) + .catch(() => res.status(500)) }) /** @@ -86,19 +87,22 @@ async function connectMongo(mongoUri) { /** * Вывод информации о сервере */ -function logServerStart() { +async function logServerStart() { const [logName, sBef, sAft] = devMode ? ['Express server', ' ', ':'] : ['React Notes App', '-', ''] + const ip = await getIp() console.log(`\n${logName} has been started`) console.log(`${sBef} Local${sAft} http://localhost:${PORT}`) - console.log(`${sBef} On Your Network${sAft} http://${getIp()}:${PORT}`, '\n') + console.log(`${sBef} On Your Network${sAft} http://${ip}:${PORT}\n`) } /** * Получение ip сервера */ function getIp() { - for (let key in os.networkInterfaces()) { - const addr = os.networkInterfaces()[key][1].address - if (addr != undefined) return addr - } + return new Promise((res, rej) => { + require('dns').lookup(os.hostname(), (err, addr) => { + addr ? res(addr) : rej() + err && console.log(err) + }) + }) } diff --git a/server/socket/wss.js b/server/socket/wss.js index 5fe4464..3575687 100644 --- a/server/socket/wss.js +++ b/server/socket/wss.js @@ -8,9 +8,10 @@ const WebSocket = require('ws') function startWSS(port) { let wsCount = 0 const wsServer = new WebSocket.Server({ port }) - const wsCollection = { test: [] } + const wsCollection = {} wsServer.on('connection', (wsClient) => { wsClient.num = ++wsCount + wsClient.on("message", (data) => { try { const { userId, target } = tryParce(data) @@ -20,35 +21,28 @@ function startWSS(port) { let clients = getClients(userId) clients.push({ num: wsClient.num, - send: wsClient.send + send: msg => wsClient.send(msg) }) wsCollection[userId] = clients - - console.log("\ncollection \n", wsCollection, '\n') } if (target == "update") { getClients(wsClient.userId).forEach((wsc) => { - if ((wsClient.num === undefined) || (wsClient.num !== wsc.num)) wsc.send(data) + if ((wsClient.num === undefined) || (wsClient.num !== wsc.num)) { + wsc.send(data) + } }) } - console.log("wsClient", wsClient.num, "messaged", target) - } catch (e) { - console.log("wsClient", wsClient.num, "error on messsage", e) - } + } catch (e) { } }) - wsClient.on("open", () => console.log("wsClient", wsClient.num, "opened")) + wsClient.on("close", () => { wsCollection[wsClient.userId] = getClients(wsClient.userId).filter((wsc) => { return (wsClient.num === undefined) || (wsClient.num !== wsc.num) }) - console.log("wsClient", wsClient.num, "closed") }) - wsClient.on("error", () => console.log("wsClient", wsClient.num, "error")) - - console.log("WSS connection", wsClient.num) - }); + }) wsServer.on("close", () => console.log("WSS closed")) wsServer.on("error", () => console.log("WSS error")) wsServer.on("listening", () => console.log("WSS listening")) From df93103a6800ce2448ba5ca7beb622fb3f995d4e Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 17:59:07 +0300 Subject: [PATCH 6/7] by id fix --- client/src/NoteComponents/ModalNoteEdit.js | 4 +- client/src/NoteComponents/NoteItem.js | 12 ++--- client/src/NoteComponents/NoteList.js | 4 +- client/src/Pages/NotesPage.js | 53 ++++++++++++++++------ 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/client/src/NoteComponents/ModalNoteEdit.js b/client/src/NoteComponents/ModalNoteEdit.js index f810759..42fcfe9 100644 --- a/client/src/NoteComponents/ModalNoteEdit.js +++ b/client/src/NoteComponents/ModalNoteEdit.js @@ -25,10 +25,10 @@ function calcMaxRows() { */ function ModalNoteEdit() { /**получение контекста */ - const { removeNote, changeNoteColor, unsetEditNoteId, editNoteContent, getNoteByIndex, editNoteId } = React.useContext(NotesContext) + const { removeNote, changeNoteColor, unsetEditNoteId, editNoteContent, getNoteById, editNoteId } = React.useContext(NotesContext) /** обьект заметки */ - const note = getNoteByIndex(editNoteId) + const note = getNoteById(editNoteId) React.useEffect(() => { if (note !== null) open() }, [note]) /**хук состояния формы */ diff --git a/client/src/NoteComponents/NoteItem.js b/client/src/NoteComponents/NoteItem.js index ec836dd..1e9adcb 100644 --- a/client/src/NoteComponents/NoteItem.js +++ b/client/src/NoteComponents/NoteItem.js @@ -2,7 +2,6 @@ * @file NoteItem.js */ import React, { useContext } from 'react' -import PropTypes from 'prop-types' import NotesContext from '../Context/NotesContext' import Note, { PropTypeNote } from '../Shared/noteType/Note' import ReactMarkdown from 'react-markdown' @@ -18,7 +17,7 @@ function fixLineBreaks(mdStr) { * @param {*} param0 * */ -function NoteItem({ note = new Note(), index }) { +function NoteItem({ note = new Note() }) { /**Подключение контекста */ const { setEditNoteId, editNoteOrder } = useContext(NotesContext) @@ -39,7 +38,7 @@ function NoteItem({ note = new Note(), index }) {
{/**Заголовок и текст заметки с обработчиками отображения markdown*/} -
setEditNoteId(index)} > +
setEditNoteId(note.id)} >
@@ -56,14 +55,14 @@ function NoteItem({ note = new Note(), index }) { @@ -75,8 +74,7 @@ function NoteItem({ note = new Note(), index }) { // Валидация NoteItem.propTypes = { - note: PropTypeNote.isRequired, - index: PropTypes.number + note: PropTypeNote.isRequired } export default NoteItem diff --git a/client/src/NoteComponents/NoteList.js b/client/src/NoteComponents/NoteList.js index 1c6e3f5..d90b2f1 100644 --- a/client/src/NoteComponents/NoteList.js +++ b/client/src/NoteComponents/NoteList.js @@ -54,9 +54,9 @@ function NoteList(props) { {/**Отзывчивая сетка карточек */} {/**Рендер каждой карточки из массива */} - {Array.isArray(props.notes) ? (props.notes.sort(sortByOrder).reverse().map((note, index) => { + {Array.isArray(props.notes) ? (props.notes.sort(sortByOrder).reverse().map((note) => { return ( - + ) })) : null} diff --git a/client/src/Pages/NotesPage.js b/client/src/Pages/NotesPage.js index d37651b..754896a 100644 --- a/client/src/Pages/NotesPage.js +++ b/client/src/Pages/NotesPage.js @@ -86,9 +86,10 @@ function NotesPage() { /** * удаление карточки - * @param {*} index + * @param {*} id */ - function removeNote(index) { + function removeNote(id) { + const index = getNoteIndexById(id) const toDelete = notesArr.splice(index, 1)[0] setNotesArr([...notesArr]) loadDataToServer(toDelete, "delete") @@ -107,13 +108,11 @@ function NotesPage() { text: noteData.text, order: calcOrder(notesArr) }) - //console.log(newId, newNote.id); - const newIndex = (notesArr != null) ? notesArr.length : 0 setNotesArr( (notesArr != null) ? notesArr.concat([newNote]) : [newNote] ) loadDataToServer(newNote, "set") - setEditNoteId(newIndex) + setEditNoteId(newId) } /** @@ -121,7 +120,8 @@ function NotesPage() { * @param {*} index * @param {*} color */ - function changeNoteColor(index, color) { + function changeNoteColor(id, color) { + const index = getNoteIndexById(id) notesArr[index].color = color setNotesArr([...notesArr]) loadDataToServer(notesArr[index], "set") @@ -133,8 +133,9 @@ function NotesPage() { * @param {*} name * @param {*} text */ - function editNoteContent(index, name, text) { - if (notesArr[index]) { + function editNoteContent(id, name, text) { + const index = getNoteIndexById(id) + if (index !== null) { let note = new Note(notesArr[index]) note.name = name note.text = text @@ -149,8 +150,9 @@ function NotesPage() { * @param {number} index * @param {boolean} orderOperationFlag */ - function editNoteOrder(index, orderOperationFlag) { - if (notesArr[index]) { + function editNoteOrder(id, orderOperationFlag) { + const index = getNoteIndexById(id) + if (index !== null) { notesArr[index].order += orderOperationFlag ? 1 : -1 let fixedArr = fixOrders(notesArr) setNotesArr(fixedArr) @@ -160,9 +162,34 @@ function NotesPage() { } } + /////////// + /**функция получения карточки по id */ - function getNoteByIndex(index) { - return index !== null ? notesArr[index] : null + function getNoteById(id) { + const byId = () => { + let note = null + if (Array.isArray(notesArr)) { + notesArr.forEach((val, index) => { + if (val.id === id) note = val + }) + } + return note + } + return id !== null ? byId() : null + } + + /**функция получения индекса карточки по id */ + function getNoteIndexById(id) { + const byId = () => { + let index = null + if (Array.isArray(notesArr)) { + notesArr.forEach((val, ind) => { + if (val.id === id) index = ind + }) + } + return index + } + return id !== null ? byId() : null } /////////// @@ -186,7 +213,7 @@ function NotesPage() { /**рендер */ return ( /**Здесь отрисовываются меню добавления и редактирования заметок и сам перечнь заметок в виде динамичной отзывчивой сетки */ - +
{/**Компонент добавления карточки и модальное окно редактирования */} From dea0374bee2d7b4cf95cbb6a49231cd24662ea16 Mon Sep 17 00:00:00 2001 From: Maxim Chistyakov Date: Mon, 14 Jun 2021 19:02:28 +0300 Subject: [PATCH 7/7] ws comments and fixes --- client/src/Hooks/useDebouncedFunction.hook.js | 6 +- client/src/Hooks/useUpdaterSocket.hook.js | 21 +++++-- client/src/Pages/NotesPage.js | 28 +++++---- server/socket/wss.js | 58 ++++++++++++------- 4 files changed, 72 insertions(+), 41 deletions(-) diff --git a/client/src/Hooks/useDebouncedFunction.hook.js b/client/src/Hooks/useDebouncedFunction.hook.js index 9722687..c4771f5 100644 --- a/client/src/Hooks/useDebouncedFunction.hook.js +++ b/client/src/Hooks/useDebouncedFunction.hook.js @@ -8,10 +8,12 @@ import { useEffect, useReducer } from 'react'; */ function useDebouncedFunction(callback, delayms) { const [state, debounced] = useReducer((state, arg) => { - return { count: state.count++, arg: arg } + return { count: state.count + 1, arg: arg } }, { count: 0, arg: undefined }) useEffect(() => { - const handler = setTimeout(() => callback(state.arg), delayms) + const handler = setTimeout(() => { + if (state.count) callback(state.arg) + }, delayms) return () => clearTimeout(handler) // eslint-disable-next-line react-hooks/exhaustive-deps }, [state]) diff --git a/client/src/Hooks/useUpdaterSocket.hook.js b/client/src/Hooks/useUpdaterSocket.hook.js index 28f4893..97f68c8 100644 --- a/client/src/Hooks/useUpdaterSocket.hook.js +++ b/client/src/Hooks/useUpdaterSocket.hook.js @@ -13,6 +13,11 @@ const WS_PORT = process.env.WS_PORT || 3030 */ function useUpdaterSocket(updateData, auth) { const { request } = useHttp() + + /**дебонсированный обработчик входящего обновления */ + const debouncedUpdate = useDebouncedFunction(updateData, 200) + + /**колбек для получения url сокета */ const getSocketUrl = useCallback(async () => { return new Promise(resolve => { request("/getIp") @@ -27,15 +32,14 @@ function useUpdaterSocket(updateData, auth) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const debouncedUpdate = useDebouncedFunction(updateData, 200) - + /**опции обработки событий сокета */ const socketOptions = { onOpen: (e) => { sendRegisterMsg() console.log("ws open") }, onClose: (e) => { - console.log("ws close") + console.error("ws close") }, onMessage: (e) => { console.log("ws update message") @@ -46,9 +50,14 @@ function useUpdaterSocket(updateData, auth) { }, } + /**подключение WebSocket */ const { sendMessage } = useWebSocket(getSocketUrl, socketOptions) - const sendMsg = (target) => { + /**дебонсированный обработчик отсылаемого обновления */ + const sendUpdateMsgDebounced = useDebouncedFunction(sendUpdateMsg, 200) + + /** отпрака сообщения сокета */ + function sendMsg(target) { const msg = JSON.stringify({ userId: auth.userId, target @@ -56,12 +65,12 @@ function useUpdaterSocket(updateData, auth) { sendMessage(msg) } - const sendUpdateMsgDebounced = useDebouncedFunction(sendUpdateMsg, 200) - + /**отправка отсылаемого обновления */ function sendUpdateMsg() { sendMsg("update") } + /**отпрака сообщения регистрации сокета */ function sendRegisterMsg() { sendMsg("register") } diff --git a/client/src/Pages/NotesPage.js b/client/src/Pages/NotesPage.js index 754896a..3e0b425 100644 --- a/client/src/Pages/NotesPage.js +++ b/client/src/Pages/NotesPage.js @@ -78,7 +78,7 @@ function NotesPage() { /** * Внесение в полученных данных в массив - * @param {*} notes + * @param {Array<{}>} notes */ function setLoadedNotes(notes) { setNotesArr([...notes]) @@ -86,7 +86,7 @@ function NotesPage() { /** * удаление карточки - * @param {*} id + * @param {string} id */ function removeNote(id) { const index = getNoteIndexById(id) @@ -97,7 +97,7 @@ function NotesPage() { /** * добавление карточки - * @param {*} noteData + * @param {{}} noteData */ function addNote(noteData = {}) { const newId = String(auth.email) + String(Date.now()) + String(Math.random()) @@ -117,8 +117,8 @@ function NotesPage() { /** * Изменение цвета карточки - * @param {*} index - * @param {*} color + * @param {string} id + * @param {string} color */ function changeNoteColor(id, color) { const index = getNoteIndexById(id) @@ -129,9 +129,9 @@ function NotesPage() { /** * Изменение текстового содержания карточки - * @param {*} index - * @param {*} name - * @param {*} text + * @param {string} id + * @param {string} name + * @param {string} text */ function editNoteContent(id, name, text) { const index = getNoteIndexById(id) @@ -147,7 +147,7 @@ function NotesPage() { /** * Изменение порядка заметки - * @param {number} index + * @param {string} id * @param {boolean} orderOperationFlag */ function editNoteOrder(id, orderOperationFlag) { @@ -164,7 +164,10 @@ function NotesPage() { /////////// - /**функция получения карточки по id */ + /** + * функция получения карточки по id + * @param {string} id + */ function getNoteById(id) { const byId = () => { let note = null @@ -178,7 +181,10 @@ function NotesPage() { return id !== null ? byId() : null } - /**функция получения индекса карточки по id */ + /** + * функция получения индекса карточки по id + * @param {string} id + */ function getNoteIndexById(id) { const byId = () => { let index = null diff --git a/server/socket/wss.js b/server/socket/wss.js index 3575687..8bfe20a 100644 --- a/server/socket/wss.js +++ b/server/socket/wss.js @@ -3,45 +3,64 @@ const WebSocket = require('ws') /** * Подключение WebSocket сервера + * Он принимает сигналы об обновлении от клиента и рассылает остальным для синхронизации * @param {*} port */ function startWSS(port) { - let wsCount = 0 + /**сервер ws */ const wsServer = new WebSocket.Server({ port }) + /**коллекция ws клиентов разделенная по id пользователя */ const wsCollection = {} + // обработка событий сервера wsServer.on('connection', (wsClient) => { - wsClient.num = ++wsCount - + // уникальный номер клиента + const clientNum = Date.now() + // проверка регистрации + setTimeout(() => { + if (!wsClient.userId) wsClient.close() + }, 60 * 1000) + // обработка сообщения клиента wsClient.on("message", (data) => { try { - const { userId, target } = tryParce(data) - + // данные с клиента + const { userId, target } = JSON.parse(data) + // обработка сообщения регистрации if (target == "register") { wsClient.userId = userId - let clients = getClients(userId) - clients.push({ - num: wsClient.num, - send: msg => wsClient.send(msg) + wsClient.num = clientNum + let clients = getClients(wsClient.userId) + let match = false + clients.forEach(val => { + if (val.num == wsClient.num) match = true }) - wsCollection[userId] = clients + if (!match && wsClient.userId) { + clients.push({ + num: wsClient.num, + send: msg => wsClient.send(msg) + }) + wsCollection[wsClient.userId] = clients + } else throw new Error("ошибка регистрации сокета") } - - if (target == "update") { + // обработка сообщения об обновлении + if (target == "update" && wsClient.userId) { getClients(wsClient.userId).forEach((wsc) => { - if ((wsClient.num === undefined) || (wsClient.num !== wsc.num)) { + if ((wsClient.num !== undefined) && (wsClient.num !== wsc.num)) { wsc.send(data) } }) } - - } catch (e) { } + } catch (e) { wsClient.close() } }) - + // обработка закрытия клиента wsClient.on("close", () => { - wsCollection[wsClient.userId] = getClients(wsClient.userId).filter((wsc) => { + if (wsClient.userId) wsCollection[wsClient.userId] = getClients(wsClient.userId).filter((wsc) => { return (wsClient.num === undefined) || (wsClient.num !== wsc.num) }) }) + // обработка ошибки клиента + wsClient.on("error", () => { + wsClient.close() + }) }) wsServer.on("close", () => console.log("WSS closed")) wsServer.on("error", () => console.log("WSS error")) @@ -55,11 +74,6 @@ function startWSS(port) { function getClients(userId) { return wsCollection[userId] || [] } - - function tryParce(str) { - try { return JSON.parse(str) } - catch (e) { return str } - } } module.exports = startWSS \ No newline at end of file