refactor: lots updates
This commit is contained in:
@@ -2,7 +2,7 @@ import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import baseQuery from "./base.query";
|
||||
import BASE_URL from "../config";
|
||||
export const authApi = createApi({
|
||||
reducerPath: "auth",
|
||||
reducerPath: "authApi",
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
login: builder.mutation({
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { fetchBaseQuery } from "@reduxjs/toolkit/query";
|
||||
import toast from "react-hot-toast";
|
||||
import dayjs from "dayjs";
|
||||
import { updateToken, clearAuthData } from "../slices/auth.data";
|
||||
import { updateToken, resetAuthData } from "../slices/auth.data";
|
||||
import BASE_URL, { tokenHeader } from "../config";
|
||||
const whiteList = ["login", "checkInviteTokenValid", "getServer", "getOpenid"];
|
||||
const whiteList = [
|
||||
"login",
|
||||
"checkInviteTokenValid",
|
||||
"getServer",
|
||||
"getOpenid",
|
||||
"getMetamaskNonce",
|
||||
];
|
||||
const baseQuery = fetchBaseQuery({
|
||||
baseUrl: BASE_URL,
|
||||
prepareHeaders: (headers, { getState, endpoint }) => {
|
||||
@@ -72,14 +78,14 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
|
||||
break;
|
||||
case 401:
|
||||
{
|
||||
if (api.endpoint === "renew") {
|
||||
// toast.error("token expired, please login again");
|
||||
api.dispatch(clearAuthData());
|
||||
location.href = "/#/login";
|
||||
return;
|
||||
} else {
|
||||
toast.error("Not Authenticated");
|
||||
}
|
||||
// if (api.endpoint === "renew") {
|
||||
// toast.error("token expired, please login again");
|
||||
api.dispatch(resetAuthData());
|
||||
location.href = "/#/login";
|
||||
// } else {
|
||||
toast.error("API Not Authenticated");
|
||||
return;
|
||||
// }
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ContentTypes } from "../config";
|
||||
import { updateChannel } from "../slices/channels";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
export const channelApi = createApi({
|
||||
reducerPath: "channel",
|
||||
reducerPath: "channelApi",
|
||||
baseQuery,
|
||||
refetchOnFocus: true,
|
||||
endpoints: (builder) => ({
|
||||
|
||||
@@ -2,10 +2,9 @@ import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { removeContact } from "../slices/contacts";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
export const contactApi = createApi({
|
||||
reducerPath: "contact",
|
||||
reducerPath: "contactApi",
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
getContacts: builder.query({
|
||||
@@ -23,16 +22,6 @@ export const contactApi = createApi({
|
||||
}),
|
||||
deleteContact: builder.query({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" }),
|
||||
async onQueryStarted(uid, { dispatch, queryFulfilled }) {
|
||||
// id: who send to ,from_uid: who sent
|
||||
const patchResult = dispatch(removeContact(uid));
|
||||
try {
|
||||
await queryFulfilled;
|
||||
} catch {
|
||||
console.log("channel update failed");
|
||||
patchResult.undo();
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateAvatar: builder.mutation({
|
||||
query: (data) => ({
|
||||
|
||||
@@ -1,56 +1,49 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { batch } from "react-redux";
|
||||
import { ContentTypes } from "../config";
|
||||
import {
|
||||
// addChannelMsg,
|
||||
addChannelPendingMsg,
|
||||
removeChannelPendingMsg,
|
||||
replaceChannelPendingMsg,
|
||||
} from "../slices/message.channel";
|
||||
import {
|
||||
addUserPendingMsg,
|
||||
removeUserPendingMsg,
|
||||
replaceUserPendingMsg,
|
||||
} from "../slices/message.user";
|
||||
|
||||
// import {
|
||||
// addPendingMessage,
|
||||
// removePendingMessage,
|
||||
// } from "../slices/message.pending";
|
||||
import { addChannelMsg, removeChannelMsg } from "../slices/message.channel";
|
||||
import { addUserMsg, removeUserMsg } from "../slices/message.user";
|
||||
import { addMessage, removeMessage } from "../slices/message";
|
||||
export const onMessageSendStarted = async (
|
||||
{ id, content, type, from_uid },
|
||||
{ id, content, type = "text", from_uid, reply_mid = null },
|
||||
{ dispatch, queryFulfilled },
|
||||
from = "channel"
|
||||
) => {
|
||||
// id: who send to ,from_uid: who sent
|
||||
const ts = new Date().getTime();
|
||||
const tmpMsg = {
|
||||
id,
|
||||
content: type == "image" ? URL.createObjectURL(content) : content,
|
||||
content_type: ContentTypes[type],
|
||||
created_at: ts,
|
||||
local_mid: ts,
|
||||
from_uid,
|
||||
// unread: false,
|
||||
reply_mid,
|
||||
// 已读
|
||||
read: true,
|
||||
sending: true,
|
||||
};
|
||||
const addPendingMessage =
|
||||
from == "channel" ? addChannelPendingMsg : addUserPendingMsg;
|
||||
const replacePendingMessage =
|
||||
from == "channel" ? replaceChannelPendingMsg : replaceUserPendingMsg;
|
||||
const removePendingMessage =
|
||||
from == "channel" ? removeChannelPendingMsg : removeUserPendingMsg;
|
||||
// dispatch(addPendingMessage({ type: from, msg: tmpMsg }));
|
||||
dispatch(addPendingMessage({ ...tmpMsg }));
|
||||
const addContextMessage = from == "channel" ? addChannelMsg : addUserMsg;
|
||||
const removeContextMessage =
|
||||
from == "channel" ? removeChannelMsg : removeUserMsg;
|
||||
batch(() => {
|
||||
dispatch(addMessage({ mid: ts, ...tmpMsg }));
|
||||
dispatch(addContextMessage({ id, mid: ts }));
|
||||
});
|
||||
|
||||
try {
|
||||
const { data: server_mid } = await queryFulfilled;
|
||||
console.log("message server mid", server_mid);
|
||||
// 此处的id,是指给谁发的
|
||||
// const addMessage = from == "channel" ? addChannelMsg : addUserMsg;
|
||||
dispatch(replacePendingMessage({ id, local_mid: ts, server_mid }));
|
||||
batch(() => {
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
dispatch(addMessage({ mid: server_mid, ...tmpMsg }));
|
||||
dispatch(addContextMessage({ id, mid: server_mid }));
|
||||
});
|
||||
// dispatch(removePendingMessage({ id, mid:ts, type: from }));
|
||||
} catch {
|
||||
console.log("message send failed");
|
||||
toast.error("Send Message Failed");
|
||||
dispatch(removePendingMessage({ id, mid: ts, type: from }));
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
// patchResult.undo();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { ContentTypes } from "../config";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
|
||||
// import { updateMessage } from "../slices/message";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
export const messageApi = createApi({
|
||||
reducerPath: "message",
|
||||
reducerPath: "messageApi",
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
editMessage: builder.mutation({
|
||||
@@ -16,8 +18,11 @@ export const messageApi = createApi({
|
||||
method: "PUT",
|
||||
body: content,
|
||||
}),
|
||||
// async onQueryStarted({mid,content},{dispatch}){
|
||||
// dispatch()
|
||||
// }
|
||||
}),
|
||||
likeMessage: builder.mutation({
|
||||
reactMessage: builder.mutation({
|
||||
query: ({ mid, action }) => ({
|
||||
url: `/message/${mid}/like`,
|
||||
method: "PUT",
|
||||
@@ -31,21 +36,24 @@ export const messageApi = createApi({
|
||||
}),
|
||||
}),
|
||||
replyMessage: builder.mutation({
|
||||
query: ({ mid, content, type = "text" }) => ({
|
||||
query: ({ reply_mid, content, type = "text" }) => ({
|
||||
headers: {
|
||||
"content-type": ContentTypes[type],
|
||||
},
|
||||
url: `/message/${mid}/reply`,
|
||||
url: `/message/${reply_mid}/reply`,
|
||||
method: "POST",
|
||||
body: content,
|
||||
}),
|
||||
async onQueryStarted(param1, param2) {
|
||||
await onMessageSendStarted.call(this, param1, param2, param1.context);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useEditMessageMutation,
|
||||
useLikeMessageMutation,
|
||||
useReactMessageMutation,
|
||||
useReplyMessageMutation,
|
||||
useLazyDeleteMessageQuery,
|
||||
} = messageApi;
|
||||
|
||||
@@ -4,7 +4,7 @@ import BASE_URL from "../config";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
export const serverApi = createApi({
|
||||
reducerPath: "server",
|
||||
reducerPath: "serverApi",
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
getServer: builder.query({
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { batch } from "react-redux";
|
||||
import { addChannelMsg, removeChannelMsg } from "../../slices/message.channel";
|
||||
import { addMessage, removeMessage, updateMessage } from "../../slices/message";
|
||||
import { toggleReactionMessage } from "../../slices/message.reaction";
|
||||
import { addUserMsg, removeUserMsg } from "../../slices/message.user";
|
||||
import { updateAfterMid } from "../../slices/footprint";
|
||||
const handler = (data, dispatch, currState) => {
|
||||
const {
|
||||
mid,
|
||||
from_uid,
|
||||
created_at,
|
||||
target,
|
||||
detail: {
|
||||
mid: detailMid,
|
||||
content,
|
||||
content_type,
|
||||
type,
|
||||
properties,
|
||||
expires_in,
|
||||
detail: innerDetail,
|
||||
},
|
||||
} = data;
|
||||
const common = {
|
||||
from_uid,
|
||||
created_at,
|
||||
content,
|
||||
content_type,
|
||||
properties,
|
||||
expires_in,
|
||||
};
|
||||
switch (type) {
|
||||
case "normal":
|
||||
case "reply":
|
||||
// 更新after_mid
|
||||
dispatch(updateAfterMid(mid));
|
||||
break;
|
||||
}
|
||||
const { ready, loginUid } = 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;
|
||||
|
||||
switch (type) {
|
||||
case "normal":
|
||||
{
|
||||
batch(() => {
|
||||
dispatch(
|
||||
addMessage({
|
||||
mid,
|
||||
// 如果是自己发的消息,就是已读
|
||||
read: self,
|
||||
...common,
|
||||
})
|
||||
);
|
||||
// 未推送完 or 不是自己发的消息
|
||||
console.log("curr state", ready, loginUid, common.from_uid);
|
||||
if (!ready || loginUid != common.from_uid) {
|
||||
dispatch(appendMessage({ id, mid }));
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "reply":
|
||||
{
|
||||
batch(() => {
|
||||
dispatch(
|
||||
addMessage({
|
||||
mid,
|
||||
reply_mid: detailMid,
|
||||
// 如果是自己发的消息,就是已读
|
||||
read: self,
|
||||
...common,
|
||||
})
|
||||
);
|
||||
// 未推送完 or 不是自己发的消息
|
||||
if (!ready || loginUid != common.from_uid) {
|
||||
dispatch(appendMessage({ id, mid }));
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "reaction":
|
||||
{
|
||||
const removeContextMessage =
|
||||
to == "user" ? removeUserMsg : removeChannelMsg;
|
||||
const { type, action, content, content_type, properties } = innerDetail;
|
||||
switch (type) {
|
||||
case "like":
|
||||
{
|
||||
dispatch(
|
||||
toggleReactionMessage({ from_uid, mid: detailMid, action })
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case "delete":
|
||||
{
|
||||
batch(() => {
|
||||
dispatch(removeContextMessage({ id, mid: detailMid }));
|
||||
dispatch(removeMessage(detailMid));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "edit":
|
||||
{
|
||||
dispatch(
|
||||
updateMessage({
|
||||
mid: detailMid,
|
||||
content,
|
||||
content_type,
|
||||
properties,
|
||||
edited: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
export default handler;
|
||||
@@ -0,0 +1,173 @@
|
||||
// for SSE
|
||||
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import toast from "react-hot-toast";
|
||||
import BASE_URL from "../../config";
|
||||
import { setReady } from "../../slices/ui";
|
||||
import {
|
||||
fullfillChannels,
|
||||
addChannel,
|
||||
removeChannel,
|
||||
} from "../../slices/channels";
|
||||
import { updateUsersVersion } from "../../slices/footprint";
|
||||
import { updateUsersByLogs, updateUsersStatus } from "../../slices/contacts";
|
||||
import { resetAuthData } from "../../slices/auth.data";
|
||||
import baseQuery from "../base.query";
|
||||
import chatMessageHandler from "./chat.handler";
|
||||
const getQueryString = (params = {}) => {
|
||||
const sp = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
sp.append(key, val);
|
||||
}
|
||||
});
|
||||
return sp.toString();
|
||||
};
|
||||
export const streamingApi = createApi({
|
||||
reducerPath: "streamingApi",
|
||||
baseQuery,
|
||||
refetchOnMountOrArgChange: true,
|
||||
tagTypes: ["streaming"],
|
||||
endpoints: (builder) => ({
|
||||
initStreaming: builder.query({
|
||||
query: () => ({ url: `user/me?ts=${new Date().getTime()}` }),
|
||||
async onCacheEntryAdded(
|
||||
arg,
|
||||
{ dispatch, getState, cacheDataLoaded, cacheEntryRemoved }
|
||||
) {
|
||||
// todo
|
||||
const {
|
||||
authData: { token },
|
||||
footprint: { usersVersion, afterMid },
|
||||
} = getState();
|
||||
// create a websocket connection when the cache subscription starts
|
||||
const streaming = new EventSource(
|
||||
`${BASE_URL}/user/events?${getQueryString({
|
||||
"api-key": token,
|
||||
users_version: usersVersion,
|
||||
after_mid: afterMid,
|
||||
})}`
|
||||
);
|
||||
console.log("cache loaded 1");
|
||||
try {
|
||||
// wait for the initial query to resolve before proceeding
|
||||
await cacheDataLoaded;
|
||||
console.log("cache loaded 2");
|
||||
|
||||
streaming.onmessage = (evt) => {
|
||||
const {
|
||||
ui: { ready },
|
||||
authData: { uid: loginUid },
|
||||
} = getState();
|
||||
const data = JSON.parse(evt.data);
|
||||
const { type } = data;
|
||||
switch (type) {
|
||||
case "heartbeat":
|
||||
console.log("heartbeat");
|
||||
break;
|
||||
case "ready":
|
||||
console.log("streaming ready");
|
||||
dispatch(setReady());
|
||||
break;
|
||||
case "users_snapshot":
|
||||
{
|
||||
console.log("users snapshot");
|
||||
const { version } = data;
|
||||
dispatch(updateUsersVersion(version));
|
||||
}
|
||||
break;
|
||||
case "users_log":
|
||||
{
|
||||
console.log("users change logs");
|
||||
const { logs } = data;
|
||||
dispatch(updateUsersByLogs(logs));
|
||||
}
|
||||
break;
|
||||
case "users_state":
|
||||
case "users_state_changed":
|
||||
{
|
||||
let { type, ...rest } = data;
|
||||
const onlines =
|
||||
type == "users_state_changed" ? [rest] : rest.users;
|
||||
dispatch(updateUsersStatus(onlines));
|
||||
}
|
||||
break;
|
||||
case "kick":
|
||||
{
|
||||
console.log("kicked");
|
||||
switch (data.reason) {
|
||||
case "login_from_other_device":
|
||||
dispatch(resetAuthData());
|
||||
toast("kicked from the other device");
|
||||
break;
|
||||
case "delete_user":
|
||||
dispatch(resetAuthData());
|
||||
toast("sorry, your account has been deleted");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "related_groups":
|
||||
console.log("fullfill channels from streaming", data);
|
||||
dispatch(fullfillChannels(data.groups));
|
||||
break;
|
||||
case "joined_group":
|
||||
console.log("joined group", data.group);
|
||||
dispatch(addChannel(data.group));
|
||||
break;
|
||||
case "kick_from_group":
|
||||
console.log("kicked from group", data.gid);
|
||||
dispatch(removeChannel(data.gid));
|
||||
break;
|
||||
case "chat":
|
||||
{
|
||||
chatMessageHandler(data, dispatch, { ready, loginUid });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("sse event data", data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
streaming.onopen = () => {
|
||||
console.info("sse opened");
|
||||
};
|
||||
streaming.onerror = (err) => {
|
||||
switch (err.eventPhase) {
|
||||
case EventSource.CLOSED:
|
||||
console.log("sse error closed error");
|
||||
break;
|
||||
case EventSource.CONNECTING:
|
||||
console.log("sse error connecting error");
|
||||
// streamingApi.util.resetApiState({ type: "initStreaming" });
|
||||
streamingApi.util.invalidateTags("streaming");
|
||||
streamingApi.util.updateQueryData("initStreaming");
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error("sse error error", err);
|
||||
// renewToken({ token, refreshToken });
|
||||
break;
|
||||
}
|
||||
};
|
||||
} catch {
|
||||
// no-op in case `cacheEntryRemoved` resolves before `cacheDataLoaded`,
|
||||
// in which case `cacheDataLoaded` will throw
|
||||
}
|
||||
// cacheEntryRemoved will resolve when the cache subscription is no longer active
|
||||
await cacheEntryRemoved;
|
||||
// perform cleanup steps once the `cacheEntryRemoved` promise resolves
|
||||
console.log("close streaming");
|
||||
streaming.close();
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useInitStreamingQuery,
|
||||
useLazyInitStreamingQuery,
|
||||
} = streamingApi;
|
||||
Reference in New Issue
Block a user