import React, {useEffect} from 'react' import { gql } from '../helpers' import { actionChatsCount } from '../actions' import { createStore, combineReducers, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import { actionOnMsg, actionOnChat, actionOnChatLeft, actionFullLogout, } from '../actions' import io from 'socket.io-client' import { actionFullChatList, actionFullMsgsByChat, actionGetChatById } from "../actions"; export function promiseReducer(state, { type, status, payload, error, name }) { if (!state) { return {} } if (type === 'PROMISE') { return { ...state, [name]: { status: status, payload: (status === 'PENDING' && state[name] && state[name].payload) || payload, error: error, }, } //для пользы при работе с промисами надо бы пока PENDING не делать payload undefined //при наличии старого payload } return state } const actionPending = (name) => ({ type: 'PROMISE', status: 'PENDING', name }) const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload, }) const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error, }) export const actionPromise = (name, promise) => async (dispatch) => { dispatch(actionPending(name)) try { let data = await promise dispatch(actionResolved(name, data)) return data } catch (error) { dispatch(actionRejected(name, error)) } } // ------------------ function jwtDecode(token) { try { const decoded = JSON.parse(atob(token.split('.')[1])) return decoded } catch (err) { console.log(err) } } export function authReducer(state, { type, token }) { if (!state) { if (localStorage.authToken) { token = localStorage.authToken type = 'AUTH_LOGIN' } else { return {} } } if (type === 'AUTH_LOGIN') { const payload = jwtDecode(token) if (typeof payload === 'object') { localStorage.authToken = token return { ...state, token, payload, } } else { console.error( 'Токен ' + localStorage.authToken + ' неверный и был удален' ) delete localStorage.authToken return state || {} } } if (type === 'AUTH_LOGOUT') { delete localStorage.authToken return {} } return state } export const actionAuthLogin = (token) => ({ type: 'AUTH_LOGIN', token }) export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' }) // ----------------------------------- // const chats = { // chats: [ // {_id: '333', title: 'moiChat2', avatar: null, lastModified: 'segodnya', owner: {login: 'ya'}, members:[]}, // {_id: '222', title: 'moiChat', avatar: null, lastModified: 'vchera', owner: {login: 'ya'}, members:[]} // ], // messages: { // '222': [ // {text: 'uuu'}, {text: 'uuuuuu'} // ], // '333': [ // {text: 'UUU'}, {text: 'U'} // ] // } // } // payload - или массив чатов или массив сообщений или 1 чат export function chatsReducer(state, { type, payload }) { if (!state) { return { } } function refreshMsgs(newMsgs, oldMsgs) { const msgState = [...oldMsgs] for (const newMsg of newMsgs || []) { const currIndex = msgState.findIndex( (oldMsg) => oldMsg._id === newMsg._id ) if (currIndex === -1) { msgState.push(newMsg) } else { msgState[currIndex] = newMsg } } const newMsgState = msgState.sort((a, b) => { if (a._id > b._id) { return 1 } if (a._id < b._id) { return -1 } return 0 }) return newMsgState } function getInfoAboutNext(msgState) { const informedState = [] for (let i = 0; i < msgState.length; i++) { const msg = msgState[i] msg.nextMsg = msgState[i + 1] || null informedState.push(msg) } return informedState } function sortChats(unsortedChats) { return Object.fromEntries( Object.entries(unsortedChats).sort((a, b) => { if (a[1].lastModified > b[1].lastModified) { return -1 } if (a[1].lastModified < b[1].lastModified) { return 1 } return 0 }) ) } const types = { CHATS() { if (payload) { const oldChats = { ...state } // перебираем новые чаты for (const chat of payload) { // находим старый чат с ид нового const oldChat = oldChats[chat._id] // если его еще нет, то просто записываем if (!oldChat) { oldChats[chat._id] = { ...chat } // если есть, то идем по свойствам нового чата } else for (const key in chat) { // записываем значение свойства нового и старого чата const oldValue = oldChat[key] const newValue = chat[key] // проверяем наличие значений // если оба значения или только новое, переходим к проверке на массив сообщений if (newValue) { // если массив сообщений, то вызываем соответствующие функции слияния if (key === 'messages') { oldChats[chat._id][key] = getInfoAboutNext( refreshMsgs(newValue, oldValue) ) } else { oldChats[chat._id][key] = newValue //----------------------------------- } // если есть только старое, записать старое (можно ничего не делать) } else { oldChats[chat._id][key] = oldValue //------------------------------------- } } } const newState = sortChats(oldChats) return newState } return state }, CHAT_LEFT() { const { [payload._id]: removed, ...newState } = state // eslint-disable-line return newState }, CHATS_CLEAR() { return {} }, MSGS() { if (payload && payload.length > 0) { const chatId = payload[0]?.chat?._id const msgState = state[chatId]?.messages || [] const newMsgState = getInfoAboutNext( refreshMsgs(payload, msgState) ) const newState = { ...state, [chatId]: { ...state[chatId], messages: newMsgState, }, } return newState } return state }, } if (type in types) { return types[type]() } return state } export const actionChatList = (chats) => ({ type: 'CHATS', payload: chats }) export const actionChatOne = (chat) => ({ type: 'CHATS', payload: [chat] }) export const actionChatLeft = (chat) => ({ type: 'CHAT_LEFT', payload: chat }) export const actionChatsClear = () => ({ type: 'CHATS_CLEAR' }) export const actionMsgList = (msgs) => ({ type: 'MSGS', payload: msgs }) export const actionMsgOne = (msg) => ({ type: 'MSGS', payload: [msg] }) // ------------------------------- export const actionUserFindOne = (userId, name = 'findUserOne') => actionPromise( name, gql( `query findUserOne($q: String) { UserFindOne (query: $q){ _id createdAt login nick avatar { _id url } chats{ avatar { _id url } messages { _id createdAt text owner { _id createdAt login nick } media { _id createdAt text url originalFileName type } } _id createdAt title lastMessage { _id createdAt text } } } } `, { q: JSON.stringify([{ _id: userId }]), } ) ) export const actionAboutMe = () => async (dispatch, getState) => { let { auth } = getState() let id = auth?.payload?.sub?.id if (id) { await dispatch(actionUserFindOne(id, 'myProfile')) await dispatch(actionChatsCount(id)) } } // ----------------------- // let initialState = localStorage.getItem('state') export const store = createStore( combineReducers({ auth: authReducer, chats: chatsReducer, promise: promiseReducer, }), // initialState ? {chats: JSON.parse(initialState)} : {}, applyMiddleware(thunk) ) store.dispatch(actionAboutMe()) // store.subscribe(() => console.log(store.getState())) // window.onbeforeunload = () => window.localStorage.setItem('state', JSON.stringify(store.getState().chats) ) /////////////////////////////////////////////////////////////////// export const socket = io('ws://chat.ed.asmer.org.ua') socket.on('jwt_ok', (data) => console.log(data)) socket.on('jwt_fail', (error) => { console.log(error) store.dispatch(actionFullLogout()) }) socket.on('msg', (msg) => { console.log('пришло смс') store.dispatch(actionOnMsg(msg)) }) socket.on('chat', (chat) => { console.log('нас добавили в чат') store.dispatch(actionOnChat(chat)) const state = store.getState() socket.disconnect(true) socket.connect() socket.emit('jwt', state.auth.token) }) socket.on('chat_left', (chat) => { console.log('нас выкинули из чата') store.dispatch(actionOnChatLeft(chat)) }) store.subscribe(() => console.log(store.getState())); console.log(store.getState()) if(store.getState().auth.token){ store.dispatch(actionFullChatList(store.getState().auth.payload?.sub.id)) } const bodyColor = () => {return document.body.style.background = '#eee'} bodyColor() // store.dispatch(actionGetChatById("633b2f0c55e76f7ddb1eae97"))