index.js 12 KB

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