Last active: 10 months ago
Zego message slice
import { AppThunk, RootState } from '@/store';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { notification } from 'antd';
import { ZegoUser } from 'zego-express-engine-webrtc/sdk/src/common/zego.entity';
import type { ZegoRoomStateChangedReason } from 'zego-express-engine-webrtm/sdk/code/zh/ZegoExpressEntity';
import { ZegoBroadcastMessageInfo } from 'zego-express-engine-webrtm/sdk/code/zh/ZegoExpressEntity';
/**
* 是否是房间类消息
*/
export function isRoomMsg(
msg: RoomMessage | CharacterMessage
): msg is RoomMessage {
return msg.type === 'room';
}
/**
* 是否是角色消息
*/
export function isCharMsg(
msg: RoomMessage | CharacterMessage
): msg is CharacterMessage {
return msg.type === 'character';
}
// 房间
export type RoomType = 'room';
// 角色
export type CharType = 'character';
// 登录 登出 聊天消息
export type RoomAction = 'login' | 'logout' | 'chat';
// 移动 坐下
export type CharAction = 'move' | 'sit-down';
// 消息的类型 房间、角色 消息
export type MessageType = RoomType | CharType;
// 从即构收到的消息
export type ReceivedMessage<M extends RoomMessage | CharacterMessage> = {
message: M;
} & Omit<ZegoBroadcastMessageInfo, 'message'>;
// 验证接受到的消息
export type ValidMsgType = {
room: ReceivedMessage<RoomMessage>[];
character: ReceivedMessage<CharacterMessage>[];
};
// 验证消息指定属性
export type ValidMessage<T extends 'data' | 'action'> = {
room: RoomMessage[T];
character: CharacterMessage[T];
};
// 验证消息本体
export type ValidMsg = {
room: RoomMessage;
character: CharacterMessage;
};
/** 消息结构 */
export type BaseMessage<T, A> = {
// nanoid
id: string;
type: T;
action: A;
/* data: D; */
};
/** 房间消息 */
export type RoomDataMap = {
login: {
username: string;
};
logout: {
age: string;
};
chat: {
msg: string;
};
};
export type RoomData<K extends RoomAction> = RoomDataMap[K];
export type RoomMessage<
T = RoomType,
// 登录 登出
A extends RoomAction = RoomAction,
> = {
data: RoomData<A>;
} & BaseMessage<T, A>;
/** 角色消息 */
export type CharacterDataMap = {
move: {
position: {
x: number;
y: number;
z: number;
};
};
'sit-down': boolean;
};
export type CharacterData<T extends CharAction> = CharacterDataMap[T];
export type CharacterMessage<
T = CharType,
// 移动 坐下
A extends CharAction = CharAction,
> = {
data: CharacterData<A>;
} & BaseMessage<T, A>;
/**
* 房间以及自身在房间中的状态
*/
export type RoomState = {
// 即构 APP ID
appID: number;
// 当前房间 ID
roomID: string;
// 登录即构用到 tokne
token: string;
// 当前房间状态
roomState: ZegoRoomStateChangedReason | '';
/* export type ZegoUser = {
// 即构当前用户 ID 与参展商手机号相同
userId: string;
// 用户名
userName: string;
}; */
} & ZegoUser;
export const initRoomState: RoomState = {
appID: 0,
roomID: '',
// 用户 id 为当前登陆展台的手机号,必须先登陆
userID: '',
userName: '',
token: '',
roomState: '',
};
// 房间内用户
export type RoomUser = ZegoUser;
/**
* 即构状态
*/
export interface ZegoState {
// 房间状态
roomState: RoomState;
// 房间内用户
users: RoomUser[];
// 收到的房间消息
roomMessage: ReceivedMessage<RoomMessage>[];
// 收到的角色消息
charMessage: ReceivedMessage<CharacterMessage>[];
// 已发送的所有消息
sentMessage: (CharacterMessage | RoomMessage)[];
}
const initialState: ZegoState = {
roomState: initRoomState,
users: [],
roomMessage: [],
charMessage: [],
sentMessage: [],
};
/**
* 3.0 即构状态
*/
export const zego = createSlice({
name: 'zego-v3',
initialState,
reducers: {
reset() {
return initialState;
},
// 房间状态
updateRoom(s, action: PayloadAction<Partial<RoomState>>) {
s.roomState = {
...s.roomState,
...action.payload,
};
},
/**
* 操作房间内用户列表
*
* @param type:
* - add 添加参数数组到当前用户列表
* - remove 从当前用户列表中移除参数数组中的用户
* - reset 将当前用户列表替换为参数 users
*/
updateUsers(
s,
action: PayloadAction<{
users: RoomUser[];
type: 'add' | 'remove' | 'reset';
}>
) {
switch (action.payload.type) {
case 'add':
s.users = s.users.concat(action.payload.users);
action.payload.users.forEach((user) => {
notification.open({
message: 'user joined',
description: `user ${user.userName} joined`,
});
});
break;
case 'remove':
const ids = action.payload.users.reduce<string[]>((prev, cur) => {
const user: ZegoUser | undefined = s.users.find(
(u) => u.userID === cur.userID
);
if (!user) return prev;
return prev.concat(user.userID);
}, []);
ids.forEach(
(id) => (s.users = s.users.filter((u) => u.userID !== id))
);
action.payload.users.forEach((user) => {
notification.open({
message: 'user left',
description: `user ${user.userName} left`,
});
});
break;
case 'reset':
s.users = action.payload.users;
break;
default:
throw new Error(
`updateUsers: wrong param type ${action.payload.type}`
);
}
},
/**
* 根据消息类型来将消息添加到特定的消息队列中
*
* @param type 消息类型 房间消息 角色消息
* @param msg 消息实际内容
*
* 断言,reducer 无法正确推断范型,使用 AppThunk 代替
*/
addRoomMsg<K extends keyof ValidMsgType>(
s: ZegoState,
action: PayloadAction<{
type: K;
msg: ValidMsgType[K];
}>
) {
const map: {
[key in keyof ValidMsgType]: 'roomMessage' | 'charMessage';
} = {
room: 'roomMessage',
character: 'charMessage',
};
const key = map?.[action.payload.type];
if (!key) {
throw new Error(
`zego slice addRoomMsg wrong type ${action.payload.type}`
);
}
/** @ts-ignore 由 AppThunk addRoomMsg 约束 */
s[key] = s[key].concat(action.payload.msg);
},
sentMsg<K extends keyof ValidMsgType>(
s: ZegoState,
action: PayloadAction<{
type: K;
msg: ValidMsg[K];
}>
) {
s.sentMessage.push(action.payload.msg);
},
},
});
export const { reset, updateRoom, updateUsers } = zego.actions;
/**
* 根据消息类型来将消息添加到特定的消息队列中
*
* 从即构接受的消息为数组,
* 记录自身发送的消息时为单个消息
*
* @param type 消息类型 房间消息 角色消息
* @param msg 消息实际内容
*/
export const addRoomMsg =
<K extends MessageType>({
type,
msg,
}: {
type: K;
msg: ValidMsgType[K] | ValidMsg[K];
}): AppThunk =>
async (dispatch, getState) => {
if (Array.isArray(msg)) {
dispatch(zego.actions.addRoomMsg({ type, msg }));
} else {
dispatch(zego.actions.sentMsg({ type, msg }));
}
};
export default zego.reducer;
// selectors
export const selectRoomState = (s: RootState) => s.zegov3.roomState;
export const selectUser = (s: RootState) => s.zegov3.users;
export const selectRoomMessage = (s: RootState) => s.zegov3.roomMessage;
export const selectCharacterMessage = (s: RootState) => s.zegov3.charMessage;