refactor: add typescript support to project

This commit is contained in:
HD
2022-06-20 17:44:42 +08:00
parent 45419bbcf4
commit c2c051d94c
25 changed files with 407 additions and 222 deletions
+5 -8
View File
@@ -3,6 +3,7 @@ import { nanoid } from "@reduxjs/toolkit";
import baseQuery from "./base.query";
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
import BASE_URL, { KEY_DEVICE_KEY } from "../config";
import { AuthData } from '../../types/auth';
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
@@ -27,7 +28,7 @@ export const authApi = createApi({
device_token: "test"
}
}),
transformResponse: (data) => {
transformResponse: (data: AuthData) => {
const { avatar_updated_at } = data.user;
data.user.avatar =
avatar_updated_at == 0
@@ -116,12 +117,10 @@ export const authApi = createApi({
})
}),
getCredentials: builder.query({
query: () => ({
url: `/token/credentials`
})
query: () => ({ url: "/token/credentials" })
}),
logout: builder.query({
query: () => ({ url: `token/logout` }),
query: () => ({ url: "token/logout" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
@@ -132,9 +131,7 @@ export const authApi = createApi({
}
}),
getInitialized: builder.query({
query: () => ({
url: `/admin/system/initialized`
}),
query: () => ({ url: "/admin/system/initialized" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data: isInitialized } = await queryFulfilled;
@@ -3,6 +3,7 @@ import toast from "react-hot-toast";
import dayjs from "dayjs";
import { updateToken, resetAuthData } from "../slices/auth.data";
import BASE_URL, { tokenHeader } from "../config";
const whiteList = [
"login",
"register",
@@ -20,6 +21,7 @@ const whiteList = [
"getInitialized",
"createAdmin"
];
const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL,
prepareHeaders: (headers, { getState, endpoint }) => {
@@ -31,6 +33,7 @@ const baseQuery = fetchBaseQuery({
return headers;
}
});
let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
if (waitingForRenew) {
@@ -9,6 +9,7 @@ import { removeChannelSession } from "../slices/message.channel";
import { removeReactionMessage } from "../slices/message.reaction";
import { onMessageSendStarted } from "./handlers";
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
export const channelApi = createApi({
reducerPath: "channelApi",
baseQuery,
+1 -24
View File
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { KEY_EXPIRE, KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from '../config';
import { AuthData, AuthToken } from '../../types/auth';
interface State {
initialized: boolean;
@@ -25,30 +26,6 @@ const emptyState: State = {
refreshToken: null
};
interface AuthToken {
// common
server_id: string;
token: string;
refresh_token: string;
expired_in: number;
}
interface User {
uid: number;
email: string;
name: string;
gender: number;
language: string;
is_admin: boolean;
avatar_updated_at: number;
create_by: string;
}
interface AuthData extends AuthToken {
initialized?: boolean;
user: User;
}
const authDataSlice = createSlice({
name: 'authData',
initialState,
-86
View File
@@ -1,86 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
import { getNonNullValues } from "../../common/utils";
import BASE_URL from "../config";
const initialState = {
ids: [],
byId: {}
};
const contactsSlice = createSlice({
name: `contacts`,
initialState,
reducers: {
resetContacts() {
return initialState;
},
fullfillContacts(state, action) {
console.log("set Contacts store", action);
const contacts = action.payload || [];
state.ids = contacts.map(({ uid }) => uid);
state.byId = Object.fromEntries(
contacts.map((c) => {
const { uid } = c;
return [uid, c];
})
);
},
removeContact(state, action) {
const uid = action.payload;
state.ids = state.ids.filter((i) => i != uid);
delete state.byId[uid];
},
updateUsersByLogs(state, action) {
const changeLogs = action.payload;
changeLogs.forEach(({ action, uid, ...rest }) => {
switch (action) {
case "update":
{
const vals = getNonNullValues(rest);
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]}`;
}
});
}
}
break;
case "create":
{
state.byId[uid] = { uid, ...rest };
const idx = state.ids.findIndex((i) => i == uid);
if (idx == -1) {
state.ids.push(uid);
}
}
break;
case "delete":
{
const idx = state.ids.findIndex((i) => i == uid);
if (idx > -1) {
state.ids.splice(idx, 1);
delete state.byId[uid];
}
}
break;
default:
break;
}
});
},
updateUsersStatus(state, action) {
const onlines = action.payload;
onlines.forEach((data) => {
const { uid, online = false } = data;
// console.log("update user status", curr, online);
if (state.byId[uid]) {
state.byId[uid].online = online;
}
});
}
}
});
export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
contactsSlice.actions;
export default contactsSlice.reducer;
+106
View File
@@ -0,0 +1,106 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getNonNullValues } from '../../common/utils';
import BASE_URL from '../config';
import { User } from '../../types/auth';
import { UserLog, UserState } from '../../types/sse';
export interface StoredUser extends User {
online?: boolean;
avatar: string;
}
export interface State {
ids: number[];
byId: { [id: number]: StoredUser | undefined };
}
const initialState: State = {
ids: [],
byId: {}
};
const contactsSlice = createSlice({
name: 'contacts',
initialState,
reducers: {
resetContacts() {
return initialState;
},
fullfillContacts(state, action: PayloadAction<User[]>) {
console.log('set Contacts store', action);
const contacts = action.payload || [];
state.ids = contacts.map(({ uid }) => uid);
// old code
// state.byId = Object.fromEntries(
// contacts.map((c) => {
// const { uid } = c;
// return [uid, c];
// })
// );
contacts.forEach(u => {
state.byId[u.uid] = {
...u,
// todo: add avatar field
avatar: ''
};
});
},
removeContact(state, action: PayloadAction<number>) {
const uid = action.payload;
state.ids = state.ids.filter((i) => i != uid);
delete state.byId[uid];
},
updateUsersByLogs(state, action: PayloadAction<UserLog[]>) {
const changeLogs = action.payload;
changeLogs.forEach(({ action, uid, ...rest }) => {
switch (action) {
case 'update': {
const vals = getNonNullValues(rest);
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]}`;
}
});
}
break;
}
case 'create': {
// todo: missing properties avatar, create_by
state.byId[uid] = { uid, ...rest };
const idx = state.ids.findIndex((i) => i == uid);
if (idx == -1) {
state.ids.push(uid);
}
break;
}
case 'delete': {
const idx = state.ids.findIndex((i) => i == uid);
if (idx > -1) {
state.ids.splice(idx, 1);
delete state.byId[uid];
}
break;
}
default:
break;
}
});
},
updateUsersStatus(state, action: PayloadAction<UserState[]>) {
action.payload.forEach((data) => {
const { uid, online = false } = data;
// console.log("update user status", curr, online);
if (state.byId[uid]) {
state.byId[uid]!.online = online;
}
});
}
}
});
export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
contactsSlice.actions;
export default contactsSlice.reducer;
@@ -1,24 +1,32 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// import BASE_URL from "../config";
const initialState = [];
// todo: check messages type
export interface Favorite {
id: number;
messages: any[];
}
const initialState: Favorite[] = [];
const favoritesSlice = createSlice({
name: `favorites`,
initialState,
reducers: {
fullfillFavorites(state, action) {
fullfillFavorites(state, action: PayloadAction<Favorite[]>) {
return action.payload;
},
addFavorite(state, action) {
addFavorite(state, action: PayloadAction<Favorite>) {
state.push(action.payload);
},
deleteFavorite(state, action) {
deleteFavorite(state, action: PayloadAction<number>) {
const id = action.payload;
const idx = state.findIndex((f) => f.id == id);
if (idx > -1) {
state.splice(idx, 1);
}
},
populateFavorite(state, action) {
populateFavorite(state, action: PayloadAction<Favorite>) {
const { id, messages } = action.payload;
const idx = state.findIndex((fav) => fav.id == id);
if (idx > -1) {
@@ -27,6 +35,8 @@ const favoritesSlice = createSlice({
}
}
});
export const { addFavorite, deleteFavorite, fullfillFavorites, populateFavorite } =
favoritesSlice.actions;
export default favoritesSlice.reducer;
@@ -1,5 +1,15 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface State {
usersVersion: number;
afterMid: number;
readUsers: {};
readChannels: {};
muteUsers: {};
muteChannels: {};
}
const initialState: State = {
usersVersion: 0,
afterMid: 0,
readUsers: {},
@@ -7,6 +17,7 @@ const initialState = {
muteUsers: {},
muteChannels: {}
};
const footprintSlice = createSlice({
name: "footprint",
initialState,
@@ -32,13 +43,11 @@ const footprintSlice = createSlice({
muteChannels
};
},
updateUsersVersion(state, action) {
const usersVersion = action.payload;
state.usersVersion = usersVersion;
updateUsersVersion(state, action: PayloadAction<number>) {
state.usersVersion = action.payload;
},
updateAfterMid(state, action) {
const afterMid = action.payload;
state.afterMid = afterMid;
updateAfterMid(state, action: PayloadAction<number>) {
state.afterMid = action.payload;
},
updateMute(state, action) {
const payload = action.payload || {};
@@ -97,6 +106,7 @@ const footprintSlice = createSlice({
}
}
});
export const {
resetFootprint,
fullfillFootprint,
@@ -106,4 +116,5 @@ export const {
updateReadUsers,
updateMute
} = footprintSlice.actions;
export default footprintSlice.reducer;
@@ -1,5 +1,12 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {};
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ChannelMessage } from '../../types/channel';
import { EntityId } from '../../types/common';
export interface State {
[gid: number]: number[] | undefined;
}
const initialState: State = {};
const channelMsgSlice = createSlice({
name: "channelMessage",
@@ -8,41 +15,41 @@ const channelMsgSlice = createSlice({
resetChannelMsg() {
return initialState;
},
fullfillChannelMsg(state, action) {
fullfillChannelMsg(state, action: PayloadAction<State>) {
return action.payload;
},
addChannelMsg(state, action) {
const { id, mid, local_id = null } = action.payload;
if (state[id]) {
const midExsited = state[id].findIndex((id) => id == mid) > -1;
const localMsgExsited = state[id].findIndex((id) => id == local_id) > -1;
const midExsited = state[id]!.findIndex((id) => id == mid) > -1;
const localMsgExsited = state[id]!.findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return;
state[id].push(+mid);
state[id]!.push(+mid);
} else {
state[id] = [+mid];
}
},
removeChannelMsg(state, action) {
removeChannelMsg(state, action: PayloadAction<ChannelMessage & EntityId>) {
const { id, mid } = action.payload;
if (state[id]) {
const idx = state[id].findIndex((i) => i == mid);
const idx = state[id]!.findIndex((i) => i == mid);
if (idx > -1) {
// 存在 则再删除
state[id].splice(idx, 1);
state[id]!.splice(idx, 1);
}
}
},
replaceChannelMsg(state, action) {
const { id, localMid, serverMid } = action.payload;
if (state[id]) {
const localIdx = state[id].findIndex((i) => i == localMid);
const localIdx = state[id]!.findIndex((i) => i == localMid);
if (localIdx > -1 && serverMid) {
// 存在 则再删除
state[id].splice(localIdx, 1, serverMid);
state[id]!.splice(localIdx, 1, serverMid);
}
}
},
removeChannelSession(state, action) {
removeChannelSession(state, action: PayloadAction<number | number[]>) {
const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
ids.forEach((id) => {
delete state[id];
@@ -50,6 +57,7 @@ const channelMsgSlice = createSlice({
}
}
});
export const {
removeChannelSession,
resetChannelMsg,
@@ -58,4 +66,5 @@ export const {
removeChannelMsg,
replaceChannelMsg
} = channelMsgSlice.actions;
export default channelMsgSlice.reducer;
@@ -1,6 +1,7 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState: number[] = [];
const initialState = [];
const fileMessageSlice = createSlice({
name: "fileMessage",
initialState,
@@ -8,10 +9,10 @@ const fileMessageSlice = createSlice({
resetFileMessage() {
return initialState;
},
fullfillFileMessage(state, action) {
fullfillFileMessage(state, action: PayloadAction<number[]>) {
return action.payload || [];
},
addFileMessage(state, action) {
addFileMessage(state, action: PayloadAction<number>) {
const mid = action.payload;
// 加入file message 列表
const fidIdx = state.findIndex((fid) => fid == mid);
@@ -19,7 +20,7 @@ const fileMessageSlice = createSlice({
state.unshift(+mid);
}
},
removeFileMessage(state, action) {
removeFileMessage(state, action: PayloadAction<number | number[]>) {
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
// 从file message 列表删掉
@@ -31,6 +32,8 @@ const fileMessageSlice = createSlice({
}
}
});
export const { removeFileMessage, resetFileMessage, fullfillFileMessage, addFileMessage } =
fileMessageSlice.actions;
export default fileMessageSlice.reducer;
@@ -1,6 +1,13 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState = {};
// todo: check entity type
export interface State {
[mid: number]: {
[reaction: number]: number[]
} | undefined;
}
const initialState: State = {};
const reactionMessageSlice = createSlice({
name: "reactionMessage",
initialState,
@@ -8,10 +15,10 @@ const reactionMessageSlice = createSlice({
resetReactionMessage() {
return initialState;
},
fullfillReactionMessage(state, action) {
fullfillReactionMessage(state, action: PayloadAction<State>) {
return action.payload;
},
removeReactionMessage(state, action) {
removeReactionMessage(state, action: PayloadAction<number | number[]>) {
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
delete state[id];
@@ -20,39 +27,40 @@ const reactionMessageSlice = createSlice({
toggleReactionMessage(state, action) {
// rid: reaction's mid, mid: which message append to
const { from_uid, mid, rid, action: reaction } = action.payload;
console.log("msg reaction", mid, from_uid, reaction);
const ridExisted = state[rid] || false;
// 已经塞过了
if (ridExisted) return;
console.log("ssss");
// 还未塞过任何一表情
if (!state[mid]) {
state[mid] = {};
}
// 存在该表情数据
if (state[mid][reaction]) {
const reactionUids = state[mid][reaction];
if (state[mid]![reaction]) {
const reactionUids = state[mid]![reaction];
const idx = reactionUids.findIndex((id) => id == from_uid);
if (idx > -1) {
reactionUids.splice(idx, 1);
if (reactionUids.length == 0) {
// 没有表情了
delete state[mid][reaction];
delete state[mid]![reaction];
}
} else {
reactionUids.push(from_uid);
}
} else {
state[mid][reaction] = [from_uid];
state[mid]![reaction] = [from_uid];
}
// todo: ???
state[rid] = true;
}
}
});
export const {
removeReactionMessage,
resetReactionMessage,
fullfillReactionMessage,
toggleReactionMessage
} = reactionMessageSlice.actions;
export default reactionMessageSlice.reducer;
@@ -1,9 +1,15 @@
import { createSlice } from "@reduxjs/toolkit";
import BASE_URL, { ContentTypes } from "../config";
import { isImage } from "../../common/utils";
const initialState = {
export interface State {
replying: {};
}
const initialState: State = {
replying: {}
};
const messageSlice = createSlice({
name: "message",
initialState,
@@ -66,6 +72,7 @@ const messageSlice = createSlice({
}
}
});
export const {
resetMessage,
fullfillMessage,
@@ -76,4 +83,5 @@ export const {
addReplyingMessage,
removeReplyingMessage
} = messageSlice.actions;
export default messageSlice.reducer;
@@ -1,8 +1,16 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface State {
ids: string[];
// todo: check object type
byId: { [id: number]: any; };
}
const initialState: State = {
ids: [],
byId: {}
};
const userMsgSlice = createSlice({
name: "userMessage",
initialState,
@@ -10,7 +18,7 @@ const userMsgSlice = createSlice({
resetUserMsg() {
return initialState;
},
fullfillUserMsg(state, action) {
fullfillUserMsg(state, action: PayloadAction<{ [id: string]: any; }>) {
state.ids = Object.keys(action.payload);
state.byId = action.payload;
},
+29 -4
View File
@@ -1,6 +1,28 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Views } from "../config";
const initialState = {
export interface State {
online: boolean;
ready: boolean;
userGuide: {
visible: boolean;
step: number;
};
inputMode: "text";
menuExpand: boolean;
// todo
fileListView: string;
uploadFiles: { [key: string]: any };
selectMessages: { [key: string]: any };
draftMarkdown: { [key: string]: any };
draftMixedText: { [key: string]: any };
remeberedNavs: {
chat: null | string;
contact: null | string;
};
}
const initialState: State = {
online: true,
ready: false,
userGuide: {
@@ -14,11 +36,13 @@ const initialState = {
selectMessages: {},
draftMarkdown: {},
draftMixedText: {},
// todo: typo
remeberedNavs: {
chat: null,
contact: null
}
};
const uiSlice = createSlice({
name: "ui",
initialState,
@@ -41,7 +65,7 @@ const uiSlice = createSlice({
updateFileListView(state, action) {
state.fileListView = action.payload;
},
updateRemeberedNavs(state, action) {
updateRemeberedNavs(state, action: PayloadAction<{ key?: string; path: string | null }>) {
const { key = "chat", path = null } = action.payload || {};
state.remeberedNavs[key] = path;
},
@@ -59,7 +83,6 @@ const uiSlice = createSlice({
state.userGuide[key] = obj[key];
});
},
updateUploadFiles(state, action) {
const { context = "channel", id = null, operation = "add", ...rest } = action.payload;
if (!id || !context) return;
@@ -144,6 +167,7 @@ const uiSlice = createSlice({
}
}
});
export const {
fullfillUI,
setReady,
@@ -158,4 +182,5 @@ export const {
updateDraftMixedText,
updateRemeberedNavs
} = uiSlice.actions;
export default uiSlice.reducer;
@@ -1,4 +1,3 @@
// import React from 'react';
import StyledWrapper from "./styled";
export default function NotFoundPage() {
@@ -1,4 +1,5 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
`;
+28 -28
View File
@@ -1,17 +1,17 @@
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
import { Navigate } from 'react-router-dom';
import toast from 'react-hot-toast';
import { useSelector } from 'react-redux';
import StyledWrapper from './styled';
import BASE_URL from '../../app/config';
import { useRegisterMutation } from '../../app/services/contact';
import { useCheckInviteTokenValidMutation } from '../../app/services/auth';
import { useState, useEffect, ChangeEvent, FormEvent } from "react";
import { Navigate } from "react-router-dom";
import toast from "react-hot-toast";
import StyledWrapper from "./styled";
import BASE_URL from "../../app/config";
import { useRegisterMutation } from "../../app/services/contact";
import { useCheckInviteTokenValidMutation } from "../../app/services/auth";
import { useAppSelector } from "../../app/store";
export default function InvitePage() {
const { token: loginToken } = useSelector((store) => store.authData);
const [secondPwd, setSecondPwd] = useState('');
const { token: loginToken } = useAppSelector((store) => store.authData);
const [secondPwd, setSecondPwd] = useState("");
const [samePwd, setSamePwd] = useState(true);
const [token, setToken] = useState<string | null>('');
const [token, setToken] = useState<string | null>("");
const [valid, setValid] = useState(false);
// const [sp] = useSearchParams();
// const navigateTo = useNavigate();
@@ -22,7 +22,7 @@ export default function InvitePage() {
useEffect(() => {
// console.log(search);
const query = new URLSearchParams(location.search);
setToken(query.get('token'));
setToken(query.get("token"));
}, []);
useEffect(() => {
@@ -40,15 +40,15 @@ export default function InvitePage() {
}
}, [checkSuccess, isValid]);
const [input, setInput] = useState({ name: '', email: '', password: '' });
const [input, setInput] = useState({ name: "", email: "", password: "" });
const handleReg = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
if (!samePwd) {
toast.error('two passwords not same');
toast.error("two passwords not same");
return;
}
console.log('wtf', input);
console.log("wtf", input);
register({
...input,
magic_token: token,
@@ -79,53 +79,53 @@ export default function InvitePage() {
useEffect(() => {
if (!samePwd) {
toast.error('two passwords not same');
toast.error("two passwords not same");
}
}, [samePwd]);
useEffect(() => {
if (isSuccess && data) {
// 去登录
toast.success('register success, login please');
toast.success("register success, login please");
setTimeout(() => {
location.href = `/#/login`;
// navigateTo("/login",);
}, 500);
} else if (isError) {
console.log('register failed', error);
console.log("register failed", error);
switch (error.status) {
case 400:
toast.error('Register Failed: please check inputs');
toast.error("Register Failed: please check inputs");
break;
case 412:
toast.error('Register Failed: invalid token or expired');
toast.error("Register Failed: invalid token or expired");
break;
case 409: {
const tips = {
email_conflict: 'email conflict',
name_conflict: 'name conflict'
email_conflict: "email conflict",
name_conflict: "name conflict"
};
toast.error(`Register Failed: ${tips[error.data?.reason]}`);
break;
}
default:
toast.error('Register Failed');
toast.error("Register Failed");
break;
}
}
}, [data, isSuccess, isError, error]);
const { email, password, name } = input;
if (loginToken) return <Navigate replace to="/"/>;
if (!token) return 'token not found';
if (checkLoading) return 'checking token valid';
if (!valid) return 'invite token expires or invalid';
if (loginToken) return <Navigate replace to="/" />;
if (!token) return "token not found";
if (checkLoading) return "checking token valid";
if (!valid) return "invite token expires or invalid";
return (
<StyledWrapper>
<div className="form animate__animated animate__fadeInDown animate__faster">
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo"/>
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Sign Up to Rustchat</h2>
<span className="desc">Please enter your details.</span>
</div>
+28
View File
@@ -0,0 +1,28 @@
export interface AuthToken {
// common
server_id: string;
token: string;
refresh_token: string;
expired_in: number;
}
// todo: check gender values
export type Gender = 0 | 1;
export interface User {
// avatar: string; // todo: check transform data in redux slice
uid: number;
email: string;
name: string;
gender: Gender;
language: string;
is_admin: boolean;
avatar_updated_at: number;
create_by: string;
}
export interface AuthData extends AuthToken {
initialized?: boolean;
user: User;
}
+6 -1
View File
@@ -3,6 +3,11 @@ export interface ChannelMember {
}
// todo: check message fields
export interface ChannelMessage {
mid: number;
}
export type ContentType =
'text/plain' |
'text/markdown' |
@@ -22,7 +27,7 @@ export interface PinnedMessage {
export interface Channel {
gid: number;
icon: string; // added by frontend
// icon: string; // todo: check
owner: number;
name: string;
description: string;
+3
View File
@@ -0,0 +1,3 @@
export interface EntityId {
id: number;
}
+3 -2
View File
@@ -1,7 +1,8 @@
import { PrecacheEntry } from "workbox-precaching/src/_types";
export declare global {
interface Window {
__WB_MANIFEST: any; // todo
skipWaiting: any; // todo
__WB_MANIFEST: Array<PrecacheEntry | string>;
skipWaiting: () => void;
}
}
+91 -23
View File
@@ -1,70 +1,138 @@
// todo
interface ReadyEvent {
type: 'ready'
import { User } from './auth';
import { Channel, ContentType } from './channel';
export interface ReadyEvent {
type: 'ready';
}
interface UsersSnapshotEvent {
type: 'users_snapshot'
export interface UsersSnapshotEvent {
type: 'users_snapshot';
users: User[];
version: number;
}
interface UsersLogEvent {
type: 'users_log'
// todo: check if create_by field exists
export interface UserLog extends Omit<User, 'create_by'> {
log_id: number;
action: 'create' | 'update' | 'delete';
}
interface UsersStateEvent {
type: 'users_state'
export interface UsersLogEvent {
type: 'users_log';
logs: UserLog[];
}
interface UsersStateChangedEvent {
type: 'users_state_changed'
export interface UserState {
uid: number;
online: boolean;
}
export interface UsersStateEvent {
type: 'users_state';
users: UserState[];
}
interface UsersStateChangedEvent extends UserState{
type: 'users_state_changed';
}
interface UserSettingsEvent {
type: 'user_settings'
type: 'user_settings';
mute_users?: { uid: number; expired_at: number; }[];
mute_groups?: { gid: number; expired_at: number; }[];
read_index_users?: { uid: number; mid: number; }[];
read_index_groups?: { gid: number; mid: number; }[];
burn_after_reading_users?: { uid: number; expires_in: number; }[];
burn_after_reading_groups?: { gid: number; expires_in: number; }[];
}
interface UserSettingsChangedEvent {
type: 'user_settings_changed'
type: 'user_settings_changed';
from_device?: string;
add_mute_users?: { uid: number; expired_at: number; }[];
remove_mute_users?: number[];
add_mute_groups?: { gid: number; expired_at: number; }[];
remove_mute_groups?: number[];
read_index_users?: { uid: number; mid: number; }[];
read_index_groups?: { gid: number; mid: number; }[];
burn_after_reading_users?: { uid: number; expires_in: number; }[];
burn_after_reading_groups?: { gid: number; expires_in: number; }[];
}
interface RelatedGroupsEvent {
type: 'related_groups'
type: 'related_groups';
groups: Channel[];
}
interface ChatEvent {
type: 'chat'
type: 'chat';
mid: number;
from_uid: number;
created_at: number;
target: { uid: number };
detail: {
properties: {};
content: string;
content_type: ContentType;
expires_in: number;
type: 'normal';
};
}
interface KickEvent {
type: 'kick'
type: 'kick';
reason: string;
}
interface UserJoinedGroupEvent {
type: 'user_joined_group'
type: 'user_joined_group';
gid: number;
uid: number[];
}
interface UserLeavedGroupEvent {
type: 'user_leaved_group'
type: 'user_leaved_group';
gid: number;
uid: number[];
}
interface JoinedGroupEvent {
type: 'joined_group'
type: 'joined_group';
group: Channel;
}
interface KickFromGroupEvent {
type: 'kick_from_group'
type: 'kick_from_group';
gid: number;
reason: string;
}
interface GroupChangedEvent {
type: 'group_changed'
type: 'group_changed';
gid: number;
name: string;
description: string;
owner: number;
avatar_updated_at: number;
}
interface PinnedMessageUpdatedEvent {
type: 'pinned_message_updated'
type: 'pinned_message_updated';
gid: number;
mid: number;
msg: {
mid: number;
created_by: number;
created_at: number;
properties: {};
content: string;
content_type: ContentType;
}
}
interface HeartbeatEvent {
type: 'heartbeat'
type: 'heartbeat';
time: number;
}
export type ServerEvent = ReadyEvent |