index.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import React, { useEffect } from "react";
  2. import { gql } from "../helpers";
  3. import { actionChatsCount } from "../actions";
  4. import { createStore, combineReducers, applyMiddleware } from "redux";
  5. import thunk from "redux-thunk";
  6. import {
  7. actionOnMsg,
  8. actionOnChat,
  9. actionOnChatLeft,
  10. actionFullLogout,
  11. actionFullMsgsByChat
  12. } from "../actions";
  13. import io from "socket.io-client";
  14. import { actionFindUsers } from "../actions";
  15. export function promiseReducer(state, { type, status, payload, error, name }) {
  16. if (!state) {
  17. return {};
  18. }
  19. if (type === "PROMISE") {
  20. return {
  21. ...state,
  22. [name]: {
  23. status: status,
  24. payload:
  25. (status === "PENDING" &&
  26. state[name] &&
  27. state[name].payload) ||
  28. payload,
  29. error: error,
  30. },
  31. };
  32. //для пользы при работе с промисами надо бы пока PENDING не делать payload undefined
  33. //при наличии старого payload
  34. }
  35. return state;
  36. }
  37. const actionPending = (name) => ({ type: "PROMISE", status: "PENDING", name });
  38. const actionResolved = (name, payload) => ({
  39. type: "PROMISE",
  40. status: "RESOLVED",
  41. name,
  42. payload,
  43. });
  44. const actionRejected = (name, error) => ({
  45. type: "PROMISE",
  46. status: "REJECTED",
  47. name,
  48. error,
  49. });
  50. export const actionPromise = (name, promise) => async (dispatch) => {
  51. dispatch(actionPending(name));
  52. try {
  53. let data = await promise;
  54. dispatch(actionResolved(name, data));
  55. return data;
  56. } catch (error) {
  57. dispatch(actionRejected(name, error));
  58. }
  59. };
  60. // ------------------
  61. function jwtDecode(token) {
  62. try {
  63. const decoded = JSON.parse(atob(token.split(".")[1]));
  64. return decoded;
  65. } catch (err) {
  66. console.log(err);
  67. }
  68. }
  69. export function authReducer(state, { type, token }) {
  70. if (!state) {
  71. if (localStorage.authToken) {
  72. token = localStorage.authToken;
  73. type = "AUTH_LOGIN";
  74. } else {
  75. return {};
  76. }
  77. }
  78. if (type === "AUTH_LOGIN") {
  79. const payload = jwtDecode(token);
  80. if (typeof payload === "object") {
  81. localStorage.authToken = token;
  82. return {
  83. ...state,
  84. token,
  85. payload,
  86. };
  87. } else {
  88. console.error(
  89. "Токен " + localStorage.authToken + " неверный и был удален"
  90. );
  91. delete localStorage.authToken;
  92. return state || {};
  93. }
  94. }
  95. if (type === "AUTH_LOGOUT") {
  96. delete localStorage.authToken;
  97. return {};
  98. }
  99. return state;
  100. }
  101. export const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
  102. export const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
  103. // -----------------------------------
  104. export function chatsReducer(state, { type, payload }) {
  105. if (!state) {
  106. return {};
  107. }
  108. function refreshMsgs(newMsgs, oldMsgs) {
  109. const msgState = [...oldMsgs];
  110. for (const newMsg of newMsgs || []) {
  111. const currIndex = msgState.findIndex(
  112. (oldMsg) => oldMsg._id === newMsg._id
  113. );
  114. if (currIndex === -1) {
  115. msgState.push(newMsg);
  116. } else {
  117. msgState[currIndex] = newMsg;
  118. }
  119. }
  120. const newMsgState = msgState.sort((a, b) => {
  121. if (a._id > b._id) {
  122. return 1;
  123. }
  124. if (a._id < b._id) {
  125. return -1;
  126. }
  127. return 0;
  128. });
  129. return newMsgState;
  130. }
  131. function getInfoAboutNext(msgState) {
  132. const informedState = [];
  133. for (let i = 0; i < msgState.length; i++) {
  134. const msg = msgState[i];
  135. msg.nextMsg = msgState[i + 1] || null;
  136. informedState.push(msg);
  137. }
  138. return informedState;
  139. }
  140. function sortChats(unsortedChats) {
  141. return Object.fromEntries(
  142. Object.entries(unsortedChats).sort((a, b) => {
  143. if (a[1].lastModified > b[1].lastModified) {
  144. return -1;
  145. }
  146. if (a[1].lastModified < b[1].lastModified) {
  147. return 1;
  148. }
  149. return 0;
  150. })
  151. );
  152. }
  153. const types = {
  154. CHATS() {
  155. if (payload) {
  156. const oldChats = { ...state };
  157. for (const chat of payload) {
  158. const oldChat = oldChats[chat._id];
  159. if (!oldChat) {
  160. oldChats[chat._id] = { ...chat };
  161. } else
  162. for (const key in chat) {
  163. const oldValue = oldChat[key];
  164. const newValue = chat[key];
  165. if (newValue) {
  166. if (key === "messages") {
  167. oldChats[chat._id][key] = getInfoAboutNext(
  168. refreshMsgs(newValue, oldValue)
  169. );
  170. } else {
  171. oldChats[chat._id][key] = newValue;
  172. }
  173. } else {
  174. oldChats[chat._id][key] = oldValue;
  175. }
  176. }
  177. }
  178. const newState = sortChats(oldChats);
  179. return newState;
  180. }
  181. return state;
  182. },
  183. CHAT_LEFT() {
  184. const { [payload._id]: removed, ...newState } = state;
  185. return newState;
  186. },
  187. CHATS_CLEAR() {
  188. return {};
  189. },
  190. MSGS() {
  191. if (payload && payload.length > 0) {
  192. const chatId = payload[0]?.chat?._id;
  193. const msgState = state[chatId]?.messages || [];
  194. const newMsgState = getInfoAboutNext(
  195. refreshMsgs(payload, msgState)
  196. );
  197. const newState = {
  198. ...state,
  199. [chatId]: {
  200. ...state[chatId],
  201. messages: newMsgState,
  202. },
  203. };
  204. return newState;
  205. }
  206. return state;
  207. },
  208. };
  209. if (type in types) {
  210. return types[type]();
  211. }
  212. return state;
  213. }
  214. export const actionChatList = (chats) => ({ type: "CHATS", payload: chats });
  215. export const actionChatOne = (chat) => ({ type: "CHATS", payload: [chat] });
  216. export const actionChatLeft = (chat) => ({ type: "CHAT_LEFT", payload: chat });
  217. export const actionChatsClear = () => ({ type: "CHATS_CLEAR" });
  218. export const actionMsgList = (msgs) => ({ type: "MSGS", payload: msgs });
  219. export const actionMsgOne = (msg) => ({ type: "MSGS", payload: [msg] });
  220. // -------------------------------
  221. export const actionUserFindOne = (userId, name = "findUserOne") =>
  222. actionPromise(
  223. name,
  224. gql(
  225. `query findUserOne($q: String) {
  226. UserFindOne (query: $q){
  227. _id
  228. createdAt
  229. login
  230. nick
  231. avatar {
  232. _id
  233. url
  234. }
  235. chats{
  236. avatar {
  237. _id
  238. url
  239. }
  240. messages {
  241. _id
  242. createdAt
  243. text
  244. owner {
  245. _id
  246. createdAt
  247. login
  248. nick
  249. }
  250. media {
  251. _id
  252. createdAt
  253. text
  254. url
  255. originalFileName
  256. type
  257. }
  258. }
  259. _id
  260. createdAt
  261. title
  262. lastMessage {
  263. _id
  264. createdAt
  265. text
  266. }
  267. }
  268. }
  269. }
  270. `,
  271. {
  272. q: JSON.stringify([{ _id: userId }]),
  273. }
  274. )
  275. );
  276. export const actionAboutMe = () => async (dispatch, getState) => {
  277. let { auth } = getState();
  278. let id = auth?.payload?.sub?.id;
  279. if (id) {
  280. await dispatch(actionUserFindOne(id, "myProfile"));
  281. await dispatch(actionChatsCount(id));
  282. }
  283. };
  284. // -----------------------
  285. export const store = createStore(
  286. combineReducers({
  287. auth: authReducer,
  288. chats: chatsReducer,
  289. promise: promiseReducer,
  290. }),
  291. applyMiddleware(thunk)
  292. );
  293. store.dispatch(actionAboutMe());
  294. ///////////////////////////////////////////////////////////////////
  295. // if (Object.keys(store.getState().auth).length > 0) {}
  296. export const socket = window.io("ws://chat.ed.asmer.org.ua");
  297. socket.on("jwt_ok", (data) => console.log(data));
  298. socket.on("jwt_fail", (error) => {
  299. console.log(error);
  300. store.dispatch(actionFullLogout());
  301. });
  302. socket.on("msg", (msg) => {
  303. console.log("пришло смс");
  304. // store.dispatch(actionOnMsg(msg));
  305. store.dispatch(actionMsgOne(msg));
  306. });
  307. socket.on("chat", (chat) => {
  308. console.log("нас добавили в чат");
  309. store.dispatch(actionOnChat(chat));
  310. const state = store.getState();
  311. // socket.disconnect(true);
  312. // socket.connect();
  313. socket.emit("jwt", state.auth.token);
  314. });
  315. socket.on("chat_left", (chat) => {
  316. console.log("нас выкинули из чата");
  317. store.dispatch(actionOnChatLeft(chat));
  318. });
  319. store.subscribe(() => console.log(store.getState()));
  320. // console.log(store.getState());
  321. const bodyColor = () => {
  322. return (document.body.style.background = "#eee");
  323. };
  324. bodyColor();