refactor: lots updates

This commit is contained in:
zerosoul
2022-03-17 11:15:13 +08:00
parent 4bc9932d0f
commit f22c7a01cb
69 changed files with 4625 additions and 1640 deletions
+11 -1
View File
@@ -2,6 +2,7 @@ import { useState } from "react";
import localforage from "localforage";
import { useDispatch, batch } from "react-redux";
import { fullfillReactionMessage } from "./slices/message.reaction";
import { fullfillServer } from "./slices/server";
import { fullfillMessage } from "./slices/message";
import { fullfillChannelMsg } from "./slices/message.channel";
import { fullfillUserMsg } from "./slices/message.user";
@@ -38,6 +39,10 @@ const tables = [
storeName: "footprint",
description: "store user visit data",
},
{
storeName: "server",
description: "store server data",
},
// {
// storeName: "message",
// description: "store message with key-val full data",
@@ -67,6 +72,7 @@ export const useRehydrate = () => {
reactionMessage: {},
message: { replying: {} },
footprint: {},
server: {},
};
const tables = Object.keys(window.CACHE);
const results = await Promise.all(
@@ -95,6 +101,9 @@ export const useRehydrate = () => {
case "message":
rehydrateData.message[key] = data;
break;
case "server":
rehydrateData.server[key] = data;
break;
default:
break;
@@ -104,6 +113,7 @@ export const useRehydrate = () => {
);
batch(() => {
dispatch(fullfillContacts(rehydrateData.contacts));
dispatch(fullfillServer(rehydrateData.server));
console.log("fullfill channels from indexedDB");
dispatch(fullfillChannels(rehydrateData.channels));
dispatch(fullfillChannelMsg(rehydrateData.channelMessage));
@@ -114,7 +124,7 @@ export const useRehydrate = () => {
});
setIterated(true);
// console.log("iterate results", rehydrateData, results);
console.log("iterate results", rehydrateData, results);
};
return { rehydrate, rehydrated: iterated };
};
+4
View File
@@ -5,6 +5,8 @@ export const ContentTypes = {
text: "text/plain",
markdown: "text/markdown",
image: "image/png",
imageJPG: "image/jpeg",
file: "rustchat/file",
json: "application/json",
};
export const googleClientID =
@@ -15,7 +17,9 @@ export const KEY_TOKEN = "RUSTCHAT_TOKEN";
export const KEY_EXPIRE = "RUSTCHAT_TOKEN_EXPIRE";
export const KEY_REFRESH_TOKEN = "RUSTCHAT_REFRESH_TOKEN";
export const KEY_UID = "RUSTCHAT_CURR_UID";
export const KEY_DEVICE_KEY = "RUSTCHAT_DEVICE_KEY";
export const KEY_USERS_VERSION = "RUSTCHAT_USERS_VERSION";
export const KEY_AFTER_MID = "RUSTCHAT_AFTER_MID";
export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"];
export default BASE_URL;
@@ -27,6 +27,16 @@ export default async function handler({ operation, data = {}, payload }) {
await table.setItem("afterMid", afterMid);
}
break;
case "updateReadChannels":
{
await table.setItem("readChannels", data.readChannels);
}
break;
case "updateReadUsers":
{
await table.setItem("readUsers", data.readUsers);
}
break;
default:
break;
}
@@ -0,0 +1,18 @@
// import clearTable from "./clear.handler";
import { updateOnline } from "../slices/ui";
export default async function handler({ dispatch, operation }) {
switch (operation) {
case "offline":
{
dispatch(updateOnline(false));
}
break;
case "online":
{
dispatch(updateOnline(true));
}
break;
default:
break;
}
}
@@ -0,0 +1,18 @@
import clearTable from "./clear.handler";
export default async function handler({ operation, payload }) {
const table = window.CACHE["server"];
if (operation.startsWith("reset")) {
clearTable("server");
return;
}
switch (operation) {
case "updateInviteLink":
{
const data = payload;
await table.setItem("inviteLink", data);
}
break;
default:
break;
}
}
+25 -7
View File
@@ -1,12 +1,15 @@
import { createListenerMiddleware } from "@reduxjs/toolkit";
import rtkqHandler from "./handler.rtkq";
import channelsHandler from "./handler.channels";
import contactsHandler from "./handler.contacts";
import channelMsgHandler from "./handler.channel.msg";
import dmMsgHandler from "./handler.dm.msg";
import serverHandler from "./handler.server";
import messageHandler from "./handler.message";
import reactionHandler from "./handler.reaction";
import footprintHandler from "./handler.footprint";
const operations = [
"__rtkq",
"channels",
"channelMessage",
"contacts",
@@ -24,23 +27,29 @@ const listenerMiddleware = createListenerMiddleware();
listenerMiddleware.startListening({
predicate: (action, currentState, previousState) => {
const { type = "" } = action;
console.log("operation", type);
const [prefix] = type.split("/");
console.log("operation", type, operations.includes(prefix));
return operations.includes(prefix);
// console.log("listener predicate", action, currentState, previousState);
// return true;
},
effect: async (action, listenerApi) => {
if (!window.CACHE) return;
const { type = "", payload } = action;
// const key = getCacheKey(type);
// // console.log("listener effect", key, listenerApi.getState(), window.CACHE);
// if (key && window.CACHE) {
// await window.CACHE.setItem(key, listenerApi.getState()[key]);
// }
const [prefix, operation] = type.split("/");
console.log("effect opt", action);
if (!window.CACHE && prefix !== "__rtkq") return;
const state = listenerApi.getState()[prefix];
switch (prefix) {
case "__rtkq":
{
rtkqHandler({
operation,
payload,
dispatch: listenerApi.dispatch,
});
}
break;
case "channels":
{
await channelsHandler({
@@ -104,6 +113,15 @@ listenerMiddleware.startListening({
});
}
break;
case "server":
{
await serverHandler({
operation,
payload,
data: state,
});
}
break;
default:
break;
+15 -2
View File
@@ -1,6 +1,15 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import { nanoid } from "@reduxjs/toolkit";
import baseQuery from "./base.query";
import BASE_URL from "../config";
import BASE_URL, { KEY_DEVICE_KEY } from "../config";
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
if (!d) {
d = `web:${nanoid()}`;
localStorage.setItem(KEY_DEVICE_KEY, d);
}
return d;
};
export const authApi = createApi({
reducerPath: "authApi",
baseQuery,
@@ -9,7 +18,11 @@ export const authApi = createApi({
query: (credentials) => ({
url: "token/login",
method: "POST",
body: { credential: credentials, device: "web", device_token: "test" },
body: {
credential: credentials,
device: getDeviceId(),
device_token: "test",
},
}),
transformResponse: (data) => {
const { avatar_updated_at } = data.user;
+8 -2
View File
@@ -5,6 +5,7 @@ import { updateToken, resetAuthData } from "../slices/auth.data";
import BASE_URL, { tokenHeader } from "../config";
const whiteList = [
"login",
"register",
"checkInviteTokenValid",
"getServer",
"getOpenid",
@@ -64,8 +65,13 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
result = await baseQuery(args, api, extraOptions);
}
if (result?.error) {
console.log("api error", result.error.originalStatus, api.endpoint);
console.log("api error", result.error, api.endpoint);
switch (result.error.originalStatus || result.error.status) {
case "FETCH_ERROR":
{
toast.error(`${api.endpoint}: Failed to fetch`);
}
break;
case 404:
{
toast.error("Request Not Found");
@@ -84,7 +90,7 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
location.href = "/#/login";
// } else {
toast.error("API Not Authenticated");
return;
// return;
// }
}
break;
+8
View File
@@ -61,6 +61,13 @@ export const channelApi = createApi({
await onMessageSendStarted.call(this, param1, param2, "channel");
},
}),
addMembers: builder.mutation({
query: ({ id, members }) => ({
url: `group/${id}/members/add`,
method: "POST",
body: members,
}),
}),
}),
});
@@ -71,4 +78,5 @@ export const {
useGetChannelsQuery,
useCreateChannelMutation,
useSendChannelMsgMutation,
useAddMembersMutation,
} = channelApi;
+34
View File
@@ -1,5 +1,8 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import { batch } from "react-redux";
import { ContentTypes } from "../config";
import { updateReadChannels, updateReadUsers } from "../slices/footprint";
import { onMessageSendStarted } from "./handlers";
// import { updateMessage } from "../slices/message";
@@ -48,6 +51,36 @@ export const messageApi = createApi({
await onMessageSendStarted.call(this, param1, param2, param1.context);
},
}),
readMessage: builder.mutation({
query: (data) => ({
url: `/user/read-index`,
method: "POST",
body: data,
}),
async onQueryStarted(data, { dispatch, getState, queryFulfilled }) {
const { users = [], groups = [] } = data;
const { readUsers, readChannels } = getState().footprint.readUsers;
const prevUsers = users.map(({ uid }) => {
return { uid, mid: readUsers[uid] };
});
const prevChannels = users.map(({ gid }) => {
return { gid, mid: readChannels[gid] };
});
batch(() => {
dispatch(updateReadChannels(groups));
dispatch(updateReadUsers(users));
});
try {
await queryFulfilled;
} catch {
// todo
batch(() => {
dispatch(updateReadChannels(prevChannels));
dispatch(updateReadUsers(prevUsers));
});
}
},
}),
}),
});
@@ -56,4 +89,5 @@ export const {
useReactMessageMutation,
useReplyMessageMutation,
useLazyDeleteMessageQuery,
useReadMessageMutation,
} = messageApi;
+25 -1
View File
@@ -1,6 +1,6 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import BASE_URL from "../config";
import { updateInviteLink } from "../slices/server";
import baseQuery from "./base.query";
export const serverApi = createApi({
@@ -57,6 +57,29 @@ export const serverApi = createApi({
body: data,
}),
}),
createInviteLink: builder.query({
query: (expired_in = 7 * 24 * 60 * 60) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain",
},
url: `/admin/user/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text(),
}),
async onQueryStarted(expire, { dispatch, queryFulfilled, getState }) {
const {
expire: prevExp,
link: prevLink,
} = getState().server.inviteLink;
try {
const { data: link } = await queryFulfilled;
console.log("link", link);
dispatch(updateInviteLink({ expire, link }));
} catch {
dispatch(updateInviteLink({ expire: prevExp, link: prevLink }));
}
},
}),
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
@@ -79,4 +102,5 @@ export const {
useLazyGetServerQuery,
useUpdateServerMutation,
useUpdateLogoMutation,
useLazyCreateInviteLinkQuery,
} = serverApi;
+5 -4
View File
@@ -35,13 +35,14 @@ const handler = (data, dispatch, currState) => {
dispatch(updateAfterMid(mid));
break;
}
const { ready, loginUid } = currState;
const { ready, loginUid, readUsers = {}, readChannels = {} } = currState;
const to = typeof target.gid !== "undefined" ? "channel" : "user";
const appendMessage = to == "user" ? addUserMsg : addChannelMsg;
const self = from_uid == loginUid;
// 此处有点绕
const id = to == "user" ? (self ? target.uid : from_uid) : target.gid;
const readIndex = (to == "user" ? readUsers[id] : readChannels[id]) || 0;
const read = self ? true : mid < readIndex ? true : false;
switch (type) {
case "normal":
{
@@ -50,7 +51,7 @@ const handler = (data, dispatch, currState) => {
addMessage({
mid,
// 如果是自己发的消息,就是已读
read: self,
read,
...common,
})
);
@@ -70,7 +71,7 @@ const handler = (data, dispatch, currState) => {
mid,
reply_mid: detailMid,
// 如果是自己发的消息,就是已读
read: self,
read,
...common,
})
);
+27 -4
View File
@@ -9,7 +9,11 @@ import {
addChannel,
removeChannel,
} from "../../slices/channels";
import { updateUsersVersion } from "../../slices/footprint";
import {
updateUsersVersion,
updateReadChannels,
updateReadUsers,
} from "../../slices/footprint";
import { updateUsersByLogs, updateUsersStatus } from "../../slices/contacts";
import { resetAuthData } from "../../slices/auth.data";
import baseQuery from "../base.query";
@@ -58,6 +62,7 @@ export const streamingApi = createApi({
const {
ui: { ready },
authData: { uid: loginUid },
footprint: { readUsers, readChannels },
} = getState();
const data = JSON.parse(evt.data);
const { type } = data;
@@ -83,6 +88,18 @@ export const streamingApi = createApi({
dispatch(updateUsersByLogs(logs));
}
break;
case "user_settings":
case "user_settings_changed":
{
console.log("users settings");
const {
read_index_users = [],
read_index_groups = [],
} = data;
dispatch(updateReadChannels(read_index_groups));
dispatch(updateReadUsers(read_index_users));
}
break;
case "users_state":
case "users_state_changed":
{
@@ -123,7 +140,12 @@ export const streamingApi = createApi({
break;
case "chat":
{
chatMessageHandler(data, dispatch, { ready, loginUid });
chatMessageHandler(data, dispatch, {
ready,
loginUid,
readUsers,
readChannels,
});
}
break;
@@ -132,8 +154,8 @@ export const streamingApi = createApi({
break;
}
};
streaming.onopen = () => {
console.info("sse opened");
streaming.onopen = (wtf) => {
console.info("sse opened", wtf);
};
streaming.onerror = (err) => {
switch (err.eventPhase) {
@@ -148,6 +170,7 @@ export const streamingApi = createApi({
break;
default:
streaming.close();
console.error("sse error error", err);
// renewToken({ token, refreshToken });
break;
+7 -1
View File
@@ -6,6 +6,12 @@ const initialState = {
expireTime: localStorage.getItem(KEY_EXPIRE) || new Date().getTime(),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN),
};
const emptyState = {
uid: null,
token: null,
expireTime: new Date().getTime(),
refreshToken: null,
};
const authDataSlice = createSlice({
name: "authData",
initialState,
@@ -37,7 +43,7 @@ const authDataSlice = createSlice({
localStorage.removeItem(KEY_TOKEN);
localStorage.removeItem(KEY_REFRESH_TOKEN);
localStorage.removeItem(KEY_UID);
return initialState;
return emptyState;
},
setUid(state, action) {
const uid = action.payload;
+6
View File
@@ -1,5 +1,6 @@
import { createSlice } from "@reduxjs/toolkit";
import { getNonNullValues } from "../../common/utils";
import BASE_URL from "../config";
const initialState = {
ids: [],
byId: {},
@@ -37,6 +38,11 @@ const contactsSlice = createSlice({
if (state.byId[uid]) {
Object.keys(vals).forEach((k) => {
state.byId[uid][k] = vals[k];
if (k == "avatar_updated_at") {
state.byId[
uid
].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
}
});
}
}
+25 -1
View File
@@ -2,6 +2,8 @@ import { createSlice } from "@reduxjs/toolkit";
const initialState = {
usersVersion: 0,
afterMid: 0,
readUsers: {},
readChannels: {},
};
const footprintSlice = createSlice({
name: "footprint",
@@ -11,7 +13,13 @@ const footprintSlice = createSlice({
return initialState;
},
fullfillFootprint(state, action) {
return action.payload;
const {
usersVersion = 0,
afterMid = 0,
readUsers = {},
readChannels = {},
} = action.payload;
return { usersVersion, afterMid, readUsers, readChannels };
},
updateUsersVersion(state, action) {
const usersVersion = action.payload;
@@ -21,6 +29,20 @@ const footprintSlice = createSlice({
const afterMid = action.payload;
state.afterMid = afterMid;
},
updateReadUsers(state, action) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ uid, mid }) => {
state.readUsers[uid] = mid;
});
},
updateReadChannels(state, action) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ gid, mid }) => {
state.readChannels[gid] = mid;
});
},
},
});
export const {
@@ -28,5 +50,7 @@ export const {
fullfillFootprint,
updateAfterMid,
updateUsersVersion,
updateReadChannels,
updateReadUsers,
} = footprintSlice.actions;
export default footprintSlice.reducer;
+2 -2
View File
@@ -15,9 +15,9 @@ const channelMsgSlice = createSlice({
const { id, mid } = action.payload;
if (state[id]) {
if (state[id].findIndex((id) => id == mid) > -1) return;
state[id].push(mid);
state[id].push(+mid);
} else {
state[id] = [mid];
state[id] = [+mid];
}
},
removeChannelMsg(state, action) {
+4
View File
@@ -22,6 +22,10 @@ const reactionMessageSlice = createSlice({
const idx = reactionUids.findIndex((id) => id == from_uid);
if (idx > -1) {
reactionUids.splice(idx, 1);
if (reactionUids.length == 0) {
// 没有表情了
delete state[mid][reaction];
}
} else {
reactionUids.push(from_uid);
}
+3 -3
View File
@@ -18,10 +18,10 @@ const userMsgSlice = createSlice({
const { id, mid } = action.payload;
if (state.byId[id]) {
if (state.byId[id].findIndex((id) => id == mid) > -1) return;
state.byId[id].push(mid);
state.byId[id].push(+mid);
} else {
state.byId[id] = [mid];
state.ids.push(id);
state.byId[id] = [+mid];
state.ids.push(+id);
}
},
removeUserMsg(state, action) {
+35
View File
@@ -0,0 +1,35 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
inviteLink: {
link: "",
expire: 0,
},
};
const serverSlice = createSlice({
name: "server",
initialState,
reducers: {
resetServer() {
return initialState;
},
fullfillServer(state, action) {
const {
inviteLink = {
link: "",
expire: 0,
},
} = action.payload;
return { inviteLink };
},
updateInviteLink(state, action) {
const { link, expire = 7 * 24 * 60 * 60 } = action.payload;
state.inviteLink = { link, expire };
},
},
});
export const {
resetServer,
fullfillServer,
updateInviteLink,
} = serverSlice.actions;
export default serverSlice.reducer;
+5
View File
@@ -1,6 +1,7 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
online: true,
ready: false,
menuExpand: true,
setting: false,
@@ -13,6 +14,9 @@ const uiSlice = createSlice({
setReady(state) {
state.ready = true;
},
updateOnline(state, action) {
state.online = action.payload;
},
toggleMenuExpand(state) {
state.menuExpand = !state.menuExpand;
},
@@ -28,6 +32,7 @@ const uiSlice = createSlice({
});
export const {
setReady,
updateOnline,
toggleSetting,
toggleMenuExpand,
toggleChannelSetting,
+44 -1
View File
@@ -3,6 +3,7 @@ import { setupListeners } from "@reduxjs/toolkit/query";
import listenerMiddleware from "./listener.middleware";
import authDataReducer from "./slices/auth.data";
import footprintReducer from "./slices/footprint";
import serverReducer from "./slices/server";
import uiReducer from "./slices/ui";
import channelsReducer from "./slices/channels";
import contactsReducer from "./slices/contacts";
@@ -21,6 +22,7 @@ const reducer = combineReducers({
authData: authDataReducer,
ui: uiReducer,
footprint: footprintReducer,
server: serverReducer,
contacts: contactsReducer,
channels: channelsReducer,
reactionMessage: reactionMsgReducer,
@@ -49,5 +51,46 @@ const store = configureStore({
)
.prepend(listenerMiddleware.middleware),
});
setupListeners(store.dispatch);
let initialized = false;
setupListeners(
store.dispatch,
(dispatch, { onOnline, onOffline, onFocus, onFocusLost }) => {
const handleFocus = () => dispatch(onFocus());
const handleFocusLost = () => dispatch(onFocusLost());
const handleOnline = () => dispatch(onOnline());
const handleOffline = () => dispatch(onOffline());
const handleVisibilityChange = () => {
if (window.document.visibilityState === "visible") {
handleFocus();
} else {
handleFocusLost();
}
};
if (!initialized) {
if (typeof window !== "undefined" && window.addEventListener) {
// Handle focus events
window.addEventListener(
"visibilitychange",
handleVisibilityChange,
false
);
window.addEventListener("focus", handleFocus, false);
// Handle connection events
window.addEventListener("online", handleOnline, false);
window.addEventListener("offline", handleOffline, false);
initialized = true;
}
}
const unsubscribe = () => {
window.removeEventListener("focus", handleFocus);
window.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
initialized = false;
};
return unsubscribe;
}
);
export default store;