Compare commits
11 Commits
082817d6cd
...
791102c034
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
791102c034 | ||
|
|
8bcb3e74b6 | ||
|
|
e4c26a07cf | ||
|
|
f118c6b6f5 | ||
|
|
cb947429fb | ||
|
|
b3d620a329 | ||
|
|
28ffd134df | ||
|
|
f600245d3b | ||
|
|
706d811087 | ||
|
|
e5dd3ade51 | ||
|
|
83719f5f44 |
@@ -21,9 +21,10 @@ const _data_cached = JSON.parse(_dec)
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
data: {
|
data: {
|
||||||
split_sizes: number[];
|
split_sizes: number[]
|
||||||
apply(): void
|
apply(): void
|
||||||
access_token?: string
|
access_token?: string
|
||||||
|
device_id: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ type ApiCallbackMessage = {
|
|||||||
* 404: Not Found
|
* 404: Not Found
|
||||||
* 500: 伺服器端錯誤
|
* 500: 伺服器端錯誤
|
||||||
* 501: 伺服器端不支持請求的功能
|
* 501: 伺服器端不支持請求的功能
|
||||||
|
* -1: 客戶端錯誤
|
||||||
*/
|
*/
|
||||||
code: 200 | 400 | 401 | 403 | 404 | 500 | 501,
|
code: 200 | 400 | 401 | 403 | 404 | 500 | 501 | -1
|
||||||
data?: { [key: string]: unknown },
|
data?: { [key: string]: unknown }
|
||||||
}
|
}
|
||||||
export default ApiCallbackMessage
|
export default ApiCallbackMessage
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type CallMethod =
|
|||||||
"User.updateProfile" |
|
"User.updateProfile" |
|
||||||
"User.getMyInfo" |
|
"User.getMyInfo" |
|
||||||
|
|
||||||
|
"User.getInfo" |
|
||||||
|
|
||||||
"User.getMyContacts" |
|
"User.getMyContacts" |
|
||||||
"User.addContact" |
|
"User.addContact" |
|
||||||
"User.removeContacts" |
|
"User.removeContacts" |
|
||||||
|
|||||||
@@ -2,19 +2,24 @@ import { io, Socket } from 'socket.io-client'
|
|||||||
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
|
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
|
||||||
import ApiCallbackMessage from './ApiCallbackMessage.ts'
|
import ApiCallbackMessage from './ApiCallbackMessage.ts'
|
||||||
import User from "./client_data/User.ts"
|
import User from "./client_data/User.ts"
|
||||||
import data from "../Data.ts";
|
import data from "../Data.ts"
|
||||||
|
|
||||||
type UnknownObject = { [key: string]: unknown }
|
type UnknownObject = { [key: string]: unknown }
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
static myUserProfile?: User
|
static myUserProfile?: User
|
||||||
static socket?: Socket
|
static socket?: Socket
|
||||||
static events: { [key: string]: (data: UnknownObject) => UnknownObject } = {}
|
static events: { [key: string]: (data: UnknownObject) => UnknownObject | void } = {}
|
||||||
static connect() {
|
static connect() {
|
||||||
|
if (data.device_id == null)
|
||||||
|
data.device_id = crypto.randomUUID()
|
||||||
this.socket?.disconnect()
|
this.socket?.disconnect()
|
||||||
this.socket && delete this.socket
|
this.socket && delete this.socket
|
||||||
this.socket = io({
|
this.socket = io({
|
||||||
transports: ['websocket']
|
transports: ['websocket'],
|
||||||
|
auth: {
|
||||||
|
device_id: data.device_id
|
||||||
|
},
|
||||||
})
|
})
|
||||||
this.socket!.on("The_White_Silk", (name: string, data: UnknownObject, callback: (ret: UnknownObject) => void) => {
|
this.socket!.on("The_White_Silk", (name: string, data: UnknownObject, callback: (ret: UnknownObject) => void) => {
|
||||||
try {
|
try {
|
||||||
@@ -32,9 +37,12 @@ class Client {
|
|||||||
setTimeout(async () => reslove(await this.invoke(method, args, timeout)), 500)
|
setTimeout(async () => reslove(await this.invoke(method, args, timeout)), 500)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, (err: string, res: ApiCallbackMessage) => {
|
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, (err: string, res: ApiCallbackMessage) => {
|
||||||
if (err) return reject(err)
|
if (err) return resolve({
|
||||||
|
code: -1,
|
||||||
|
msg: err,
|
||||||
|
})
|
||||||
resolve(res)
|
resolve(res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -52,7 +60,7 @@ class Client {
|
|||||||
token: data.access_token
|
token: data.access_token
|
||||||
})).data as unknown as User
|
})).data as unknown as User
|
||||||
}
|
}
|
||||||
static on(eventName: ClientEvent, func: (data: UnknownObject) => UnknownObject) {
|
static on(eventName: ClientEvent, func: (data: UnknownObject) => UnknownObject | void) {
|
||||||
this.events[eventName] = func
|
this.events[eventName] = func
|
||||||
}
|
}
|
||||||
static off(eventName: ClientEvent) {
|
static off(eventName: ClientEvent) {
|
||||||
|
|||||||
19
client/api/DataCaches.ts
Normal file
19
client/api/DataCaches.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import data from "../Data.ts"
|
||||||
|
import Client from "./Client.ts"
|
||||||
|
import User from "./client_data/User.ts"
|
||||||
|
|
||||||
|
export default class DataCaches {
|
||||||
|
static userProfiles: { [key: string]: User} = {}
|
||||||
|
static async getUserProfile(userId: string): Promise<User> {
|
||||||
|
if (this.userProfiles[userId]) return this.userProfiles[userId]
|
||||||
|
const re = await Client.invoke("User.getInfo", {
|
||||||
|
token: data.access_token,
|
||||||
|
target: userId
|
||||||
|
})
|
||||||
|
if (re.code != 200) return {
|
||||||
|
id: '',
|
||||||
|
nickname: "",
|
||||||
|
}
|
||||||
|
return this.userProfiles[userId] = (re.data as unknown as User)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Tab } from "mdui"
|
import { Tab, TextField } from "mdui"
|
||||||
import useEventListener from "../useEventListener.ts"
|
import useEventListener from "../useEventListener.ts"
|
||||||
import Element_Message from "./Message.jsx"
|
import Element_Message from "./Message.tsx"
|
||||||
import MessageContainer from "./MessageContainer.jsx"
|
import MessageContainer from "./MessageContainer.tsx"
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Client from "../../api/Client.ts"
|
import Client from "../../api/Client.ts"
|
||||||
import Message from "../../api/client_data/Message.ts"
|
import Message from "../../api/client_data/Message.ts"
|
||||||
import Chat from "../../api/client_data/Chat.ts"
|
import Chat from "../../api/client_data/Chat.ts"
|
||||||
import data from "../../Data.ts"
|
import data from "../../Data.ts"
|
||||||
import { checkApiSuccessOrSncakbar } from "../snackbar.ts"
|
import { checkApiSuccessOrSncakbar, snackbar } from "../snackbar.ts"
|
||||||
import useAsyncEffect from "../useAsyncEffect.ts"
|
import useAsyncEffect from "../useAsyncEffect.ts"
|
||||||
|
|
||||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
@@ -23,6 +23,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
||||||
const tabRef = React.useRef<Tab>(null)
|
const tabRef = React.useRef<Tab>(null)
|
||||||
|
const chatPanelRef = React.useRef<HTMLElement>(null)
|
||||||
useEventListener(tabRef, 'change', () => {
|
useEventListener(tabRef, 'change', () => {
|
||||||
setTabItemSelected(tabRef.current?.value || "Chat")
|
setTabItemSelected(tabRef.current?.value || "Chat")
|
||||||
})
|
})
|
||||||
@@ -35,28 +36,71 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return checkApiSuccessOrSncakbar(re, "對話錯誤")
|
return checkApiSuccessOrSncakbar(re, "對話錯誤")
|
||||||
setChatInfo(re.data as Chat)
|
setChatInfo(re.data as Chat)
|
||||||
|
|
||||||
|
loadMore()
|
||||||
}, [target])
|
}, [target])
|
||||||
|
|
||||||
let page = 0
|
const page = React.useRef(0)
|
||||||
async function loadMore() {
|
async function loadMore() {
|
||||||
const re = await Client.invoke("Chat.getMessageHistory", {
|
const re = await Client.invoke("Chat.getMessageHistory", {
|
||||||
token: data.access_token,
|
token: data.access_token,
|
||||||
target,
|
target,
|
||||||
page,
|
page: page.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return
|
||||||
page++
|
const returnMsgs = (re.data!.messages as Message[]).reverse()
|
||||||
setMessagesList(messagesList.concat())
|
if (returnMsgs.length == 0)
|
||||||
|
return snackbar({
|
||||||
|
message: "已經沒有消息了哦~",
|
||||||
|
placement: 'top',
|
||||||
|
})
|
||||||
|
setMessagesList(returnMsgs.concat(messagesList))
|
||||||
|
|
||||||
|
if (page.current == 0 + 1)
|
||||||
|
setTimeout(() => chatPanelRef.current!.scrollTo({
|
||||||
|
top: 10000000000,
|
||||||
|
behavior: "smooth",
|
||||||
|
}), 100)
|
||||||
|
|
||||||
|
page.current++
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
interface OnMessageData {
|
||||||
|
chat: string
|
||||||
|
msg: Message
|
||||||
|
}
|
||||||
|
Client.on('Client.onMessage', (data: unknown) => {
|
||||||
|
const { chat, msg } = (data as OnMessageData)
|
||||||
|
if (target == chat) {
|
||||||
|
setMessagesList(messagesList.concat([msg]))
|
||||||
|
}
|
||||||
|
})
|
||||||
return () => {
|
return () => {
|
||||||
|
Client.off('Client.onMessage')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const inputRef = React.useRef<TextField>(null)
|
||||||
|
|
||||||
|
async function sendMessage() {
|
||||||
|
const text = inputRef.current!.value
|
||||||
|
|
||||||
|
const re = await Client.invoke("Chat.sendMessage", {
|
||||||
|
token: data.access_token,
|
||||||
|
target,
|
||||||
|
text,
|
||||||
|
}, 5000)
|
||||||
|
if (checkApiSuccessOrSncakbar(re, "發送失敗")) return
|
||||||
|
inputRef.current!.value = ''
|
||||||
|
|
||||||
|
chatPanelRef.current!.scrollTo({
|
||||||
|
top: 10000000000,
|
||||||
|
behavior: "smooth",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -76,7 +120,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
}</mdui-tab>
|
}</mdui-tab>
|
||||||
<mdui-tab value="Settings">設定</mdui-tab>
|
<mdui-tab value="Settings">設定</mdui-tab>
|
||||||
|
|
||||||
<mdui-tab-panel slot="panel" value="Chat" style={{
|
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
|
||||||
display: tabItemSelected == "Chat" ? "flex" : "none",
|
display: tabItemSelected == "Chat" ? "flex" : "none",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -84,11 +128,24 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
marginTop: "15px"
|
paddingTop: "15px",
|
||||||
|
|
||||||
}}>
|
}}>
|
||||||
<mdui-button variant="text">加載更多</mdui-button>
|
<mdui-button variant="text" onClick={() => loadMore()}>加載更多</mdui-button>
|
||||||
</div>
|
</div>
|
||||||
<MessageContainer>
|
<MessageContainer style={{
|
||||||
|
paddingTop: "15px",
|
||||||
|
flexGrow: '1',
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
messagesList.map((msg) =>
|
||||||
|
<Element_Message
|
||||||
|
key={msg.id}
|
||||||
|
userId={msg.user_id}>
|
||||||
|
{msg.text}
|
||||||
|
</Element_Message>
|
||||||
|
)
|
||||||
|
}
|
||||||
</MessageContainer>
|
</MessageContainer>
|
||||||
{
|
{
|
||||||
// 输入框
|
// 输入框
|
||||||
@@ -96,16 +153,19 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingBottom: '0.1rem',
|
paddingBottom: '2px',
|
||||||
paddingTop: '0.1rem',
|
paddingTop: '0.1rem',
|
||||||
height: '4rem',
|
height: '4rem',
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
bottom: '2px',
|
bottom: '0',
|
||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
marginRight: '4px',
|
marginRight: '4px',
|
||||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||||
}}>
|
}}>
|
||||||
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize max-rows={1} style={{
|
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize ref={inputRef as any} max-rows={1} onKeyDown={(event) => {
|
||||||
|
if (event.ctrlKey && event.key == 'Enter')
|
||||||
|
sendMessage()
|
||||||
|
}} style={{
|
||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
}}></mdui-text-field>
|
}}></mdui-text-field>
|
||||||
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
||||||
@@ -113,7 +173,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
}}></mdui-button-icon>
|
}}></mdui-button-icon>
|
||||||
<mdui-button-icon icon="send" style={{
|
<mdui-button-icon icon="send" style={{
|
||||||
marginRight: '7px',
|
marginRight: '7px',
|
||||||
}}></mdui-button-icon>
|
}} onClick={() => sendMessage()}></mdui-button-icon>
|
||||||
</div>
|
</div>
|
||||||
</mdui-tab-panel>
|
</mdui-tab-panel>
|
||||||
<mdui-tab-panel slot="panel" value="Settings" style={{
|
<mdui-tab-panel slot="panel" value="Settings" style={{
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
|
import Client from "../../api/Client.ts"
|
||||||
|
import DataCaches from "../../api/DataCaches.ts"
|
||||||
import Avatar from "../Avatar.tsx"
|
import Avatar from "../Avatar.tsx"
|
||||||
|
import useAsyncEffect from "../useAsyncEffect.ts"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Message({ userId, children, ...props }: Args) {
|
||||||
|
const isAtRight = Client.myUserProfile?.id == userId
|
||||||
|
|
||||||
|
const [ nickName, setNickName ] = React.useState("")
|
||||||
|
const [ avatarUrl, setAvatarUrl ] = React.useState<string | undefined>("")
|
||||||
|
|
||||||
|
useAsyncEffect(async () => {
|
||||||
|
const user = await DataCaches.getUserProfile(userId)
|
||||||
|
setNickName(user.nickname)
|
||||||
|
setAvatarUrl(user?.avatar)
|
||||||
|
}, [userId])
|
||||||
|
|
||||||
/**
|
|
||||||
* 一条消息
|
|
||||||
* @param { Object } param
|
|
||||||
* @param { "left" | "right" } [param.direction="left"] 消息方向
|
|
||||||
* @param { String } [param.avatar] 头像链接
|
|
||||||
* @param { String } [param.nickName] 昵称
|
|
||||||
* @returns { React.JSX.Element }
|
|
||||||
*/
|
|
||||||
export default function Message({ direction = 'left', avatar, nickName, children, ...props } = {}) {
|
|
||||||
let isAtRight = direction == 'right'
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
@@ -39,7 +49,7 @@ export default function Message({ direction = 'left', avatar, nickName, children
|
|||||||
// 发送者头像
|
// 发送者头像
|
||||||
}
|
}
|
||||||
<Avatar
|
<Avatar
|
||||||
src={avatar}
|
src={avatarUrl}
|
||||||
text={nickName}
|
text={nickName}
|
||||||
style={{
|
style={{
|
||||||
width: "43px",
|
width: "43px",
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
/**
|
interface Args extends React.HTMLAttributes<HTMLElement> {}
|
||||||
* 消息容器
|
|
||||||
* @returns { React.JSX.Element }
|
export default function MessageContainer({ children, style, ...props }: Args) {
|
||||||
*/
|
|
||||||
export default function MessageContainer({ children, style, ...props } = {}) {
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -10,7 +8,6 @@ export default function MessageContainer({ children, style, ...props } = {}) {
|
|||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: '20px',
|
marginBottom: '20px',
|
||||||
height: "100%",
|
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
{...props}>
|
{...props}>
|
||||||
@@ -45,6 +45,7 @@ export default function ContactsList({
|
|||||||
paddingRight: '10px',
|
paddingRight: '10px',
|
||||||
display: display ? undefined : 'none',
|
display: display ? undefined : 'none',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
}} {...props}>
|
}} {...props}>
|
||||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
marginTop: '5px',
|
marginTop: '5px',
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default function RecentsList({
|
|||||||
paddingRight: '10px',
|
paddingRight: '10px',
|
||||||
display: display ? undefined : 'none',
|
display: display ? undefined : 'none',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
}} {...props}>
|
}} {...props}>
|
||||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
marginTop: '5px',
|
marginTop: '5px',
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type CallMethod =
|
|||||||
"User.updateProfile" |
|
"User.updateProfile" |
|
||||||
"User.getMyInfo" |
|
"User.getMyInfo" |
|
||||||
|
|
||||||
|
"User.getInfo" |
|
||||||
|
|
||||||
"User.getMyContacts" |
|
"User.getMyContacts" |
|
||||||
"User.addContact" |
|
"User.addContact" |
|
||||||
"User.removeContacts" |
|
"User.removeContacts" |
|
||||||
|
|||||||
@@ -32,12 +32,43 @@ export default class ApiManager {
|
|||||||
static addEventListener(name: string, func: EventCallbackFunction) {
|
static addEventListener(name: string, func: EventCallbackFunction) {
|
||||||
this.event_listeners[name] = func
|
this.event_listeners[name] = func
|
||||||
}
|
}
|
||||||
|
static clients: { [key: string]: { [key: string]: SocketIo.Socket<SocketIo.DefaultEventsMap, SocketIo.DefaultEventsMap, SocketIo.DefaultEventsMap, any> } } = {}
|
||||||
|
static checkUserIsOnline(userId: string) {
|
||||||
|
return this.getUserClientSockets(userId) != null
|
||||||
|
}
|
||||||
|
static getUserClientSockets(userId: string) {
|
||||||
|
return this.clients[userId]
|
||||||
|
}
|
||||||
static initEvents() {
|
static initEvents() {
|
||||||
const io = this.socketIoServer
|
const io = this.socketIoServer
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
|
// TODO: fix ip == undefined
|
||||||
|
// https://github.com/denoland/deno/blob/7938d5d2a448b876479287de61e9e3b8c6109bc8/ext/node/polyfills/net.ts#L1713
|
||||||
|
const ip = socket.conn.remoteAddress
|
||||||
|
|
||||||
|
const deviceId = socket.handshake.auth.device_id as string
|
||||||
|
|
||||||
|
const clientInfo = {
|
||||||
|
userId: '',
|
||||||
|
deviceId,
|
||||||
|
ip,
|
||||||
|
socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('disconnect', (_reason) => {
|
||||||
|
if (clientInfo.userId == '')
|
||||||
|
console.log(chalk.yellow('[斷]') + ` ${ip} disconnected`)
|
||||||
|
else {
|
||||||
|
console.log(chalk.green('[斷]') + ` ${ip} disconnected`)
|
||||||
|
delete this.clients[clientInfo.userId][deviceId]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(chalk.yellow('[連]') + ` ${ip} connected`)
|
||||||
|
|
||||||
socket.on("The_White_Silk", (name: string, args: { [key: string]: unknown }, callback_: (ret: ApiCallbackMessage) => void) => {
|
socket.on("The_White_Silk", (name: string, args: { [key: string]: unknown }, callback_: (ret: ApiCallbackMessage) => void) => {
|
||||||
function callback(ret: ApiCallbackMessage) {
|
function callback(ret: ApiCallbackMessage) {
|
||||||
console.log(chalk.blue('[發]') + ` ${socket.request.socket.remoteAddress} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ ret.data ? (' <extras: ' + JSON.stringify(ret.data) + '>') : ''}`)
|
console.log(chalk.blue('[發]') + ` ${ip} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ret.data ? (' <extras: ' + JSON.stringify(ret.data) + '>') : ''}`)
|
||||||
return callback_(ret)
|
return callback_(ret)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -45,9 +76,9 @@ export default class ApiManager {
|
|||||||
msg: "Invalid request.",
|
msg: "Invalid request.",
|
||||||
code: 400
|
code: 400
|
||||||
})
|
})
|
||||||
console.log(chalk.red('[收]') + ` ${socket.request.socket.remoteAddress} -> ${chalk.yellow(name)} <args: ${JSON.stringify(args)}>`)
|
console.log(chalk.red('[收]') + ` ${ip} -> ${chalk.yellow(name)} <args: ${JSON.stringify(args)}>`)
|
||||||
|
|
||||||
return callback(this.event_listeners[name]?.(args) || {
|
return callback(this.event_listeners[name]?.(args, clientInfo) || {
|
||||||
code: 501,
|
code: 501,
|
||||||
msg: "Not implmented",
|
msg: "Not implmented",
|
||||||
})
|
})
|
||||||
@@ -59,7 +90,7 @@ export default class ApiManager {
|
|||||||
code: err instanceof DataWrongError ? 400 : 500,
|
code: err instanceof DataWrongError ? 400 : 500,
|
||||||
msg: "錯誤: " + err.message
|
msg: "錯誤: " + err.message
|
||||||
})
|
})
|
||||||
} catch(_e) {}
|
} catch (_e) { }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import EventCallbackFunction from "../typedef/EventCallbackFunction.ts"
|
import EventCallbackFunction from "../typedef/EventCallbackFunction.ts"
|
||||||
import ApiManager from "./ApiManager.ts"
|
import ApiManager from "./ApiManager.ts"
|
||||||
import { CallMethod } from './ApiDeclare.ts'
|
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
|
||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
import Token from "./Token.ts"
|
import Token from "./Token.ts"
|
||||||
|
import * as SocketIo from "socket.io"
|
||||||
|
|
||||||
export default abstract class BaseApi {
|
export default abstract class BaseApi {
|
||||||
abstract getName(): string
|
abstract getName(): string
|
||||||
@@ -22,18 +23,19 @@ export default abstract class BaseApi {
|
|||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
checkUserToken(user: User, token: Token) {
|
checkToken(token: Token, deviceId: string) {
|
||||||
if (!this.checkToken(token)) return false
|
|
||||||
if (token.author != user.bean.id) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
checkToken(token: Token) {
|
|
||||||
if (token.expired_time < Date.now()) return false
|
if (token.expired_time < Date.now()) return false
|
||||||
if (!User.findById(token.author)) return false
|
if (!User.findById(token.author)) return false
|
||||||
|
if (deviceId != null)
|
||||||
|
if (token.device_id != deviceId)
|
||||||
|
return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
registerEvent(name: CallMethod, func: EventCallbackFunction) {
|
registerEvent(name: CallMethod, func: EventCallbackFunction) {
|
||||||
if (!name.startsWith(this.getName() + ".")) throw Error("注冊的事件應該與接口集合命名空間相匹配: " + name)
|
if (!name.startsWith(this.getName() + ".")) throw Error("注冊的事件應該與接口集合命名空間相匹配: " + name)
|
||||||
ApiManager.addEventListener(name, func)
|
ApiManager.addEventListener(name, func)
|
||||||
}
|
}
|
||||||
|
emitToClient(client: SocketIo.Socket, name: ClientEvent, args: { [key: string]: unknown }) {
|
||||||
|
client.emit("The_White_Silk", name, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Chat from "../data/Chat.ts";
|
|||||||
import ChatPrivate from "../data/ChatPrivate.ts";
|
import ChatPrivate from "../data/ChatPrivate.ts";
|
||||||
import MessagesManager from "../data/MessagesManager.ts";
|
import MessagesManager from "../data/MessagesManager.ts";
|
||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
|
import ApiManager from "./ApiManager.ts";
|
||||||
import BaseApi from "./BaseApi.ts"
|
import BaseApi from "./BaseApi.ts"
|
||||||
import TokenManager from "./TokenManager.ts"
|
import TokenManager from "./TokenManager.ts"
|
||||||
|
|
||||||
@@ -15,14 +16,14 @@ export default class ChatApi extends BaseApi {
|
|||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
* @param target 目標對話
|
* @param target 目標對話
|
||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getInfo", (args) => {
|
this.registerEvent("Chat.getInfo", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -57,23 +58,56 @@ export default class ChatApi extends BaseApi {
|
|||||||
* 發送訊息
|
* 發送訊息
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
* @param target 目標對話
|
* @param target 目標對話
|
||||||
* @param
|
* @param text 消息内容
|
||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.sendMessage", (args) => {
|
this.registerEvent("Chat.sendMessage", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'text'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.target as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "對話不存在",
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
text: args.text as string,
|
||||||
|
user_id: token.author,
|
||||||
|
}
|
||||||
|
const id = MessagesManager.getInstanceForChat(chat).addMessage(msg)
|
||||||
|
|
||||||
|
const users: string[] = []
|
||||||
|
if (chat.bean.type == 'private') {
|
||||||
|
users.push(token.author as string)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (ApiManager.checkUserIsOnline(user)) {
|
||||||
|
const sockets = ApiManager.getUserClientSockets(user)
|
||||||
|
for (const socket of Object.keys(sockets))
|
||||||
|
this.emitToClient(sockets[socket], 'Client.onMessage', {
|
||||||
|
chat: chat.bean.id,
|
||||||
|
msg: {
|
||||||
|
...msg,
|
||||||
|
id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// TODO: EventStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 200,
|
||||||
msg: "未實現",
|
msg: "成功",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
@@ -82,14 +116,14 @@ export default class ChatApi extends BaseApi {
|
|||||||
* @param target 目標對話
|
* @param target 目標對話
|
||||||
* @param page 頁面
|
* @param page 頁面
|
||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.getMessageHistory", (args) => {
|
this.registerEvent("Chat.getMessageHistory", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target', 'page'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'page'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -104,7 +138,7 @@ export default class ChatApi extends BaseApi {
|
|||||||
code: 200,
|
code: 200,
|
||||||
msg: "成功",
|
msg: "成功",
|
||||||
data: {
|
data: {
|
||||||
messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(),
|
messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(15, args.page as number),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
4
server/api/EventBean.ts
Normal file
4
server/api/EventBean.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default class EventBean {
|
||||||
|
declare event_name: string
|
||||||
|
declare data: unknown
|
||||||
|
}
|
||||||
53
server/api/EventStorer.ts
Normal file
53
server/api/EventStorer.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { DatabaseSync } from "node:sqlite"
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import config from "../config.ts"
|
||||||
|
import User from "../data/User.ts";
|
||||||
|
import EventBean from "./EventBean.ts";
|
||||||
|
|
||||||
|
export default class EventStorer {
|
||||||
|
static database: DatabaseSync = this.init()
|
||||||
|
|
||||||
|
private static init(): DatabaseSync {
|
||||||
|
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'Events.db'))
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstanceForUser(user: User) {
|
||||||
|
return new EventStorer(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
declare user: User
|
||||||
|
constructor(user: User) {
|
||||||
|
this.user = user
|
||||||
|
|
||||||
|
EventStorer.database.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ${this.getTableName()} (
|
||||||
|
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
/* 事件 */ event_name TEXT NOT NULL,
|
||||||
|
/* 數據 */ data TEXT NOT NULL,
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
protected getTableName() {
|
||||||
|
return `events_${this.user.bean.id}`
|
||||||
|
}
|
||||||
|
addEvent(eventName: string, data: unknown) {
|
||||||
|
EventStorer.database.prepare(`INSERT INTO ${this.getTableName()} (
|
||||||
|
event_name,
|
||||||
|
data
|
||||||
|
) VALUES (?, ?);`).run(
|
||||||
|
eventName,
|
||||||
|
JSON.stringify(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
getEvents() {
|
||||||
|
return EventStorer.database.prepare(`SELECT * FROM ${this.getTableName()};`).all().map((v: any) => ({
|
||||||
|
...v,
|
||||||
|
data: JSON.parse(v.data)
|
||||||
|
})) as unknown as EventBean[]
|
||||||
|
}
|
||||||
|
clearEvents() {
|
||||||
|
EventStorer.database.prepare(`DELETE FROM ${this.getTableName()};`).run()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@ export default interface Token {
|
|||||||
auth: string
|
auth: string
|
||||||
made_time: number
|
made_time: number
|
||||||
expired_time: number
|
expired_time: number
|
||||||
|
device_id: string
|
||||||
}
|
}
|
||||||
@@ -22,22 +22,29 @@ export default class TokenManager {
|
|||||||
).toString('hex')
|
).toString('hex')
|
||||||
}
|
}
|
||||||
static decode(token: string) {
|
static decode(token: string) {
|
||||||
|
if (token == null) throw new Error('令牌為空!')
|
||||||
return JSON.parse(crypto.createDecipheriv("aes-256-gcm", normalizeKey(config.aes_key), '01234567890123456').update(
|
return JSON.parse(crypto.createDecipheriv("aes-256-gcm", normalizeKey(config.aes_key), '01234567890123456').update(
|
||||||
Buffer.from(token, 'hex')
|
Buffer.from(token, 'hex')
|
||||||
).toString()) as Token
|
).toString()) as Token
|
||||||
}
|
}
|
||||||
|
|
||||||
static make(user: User, time: number = Date.now()) {
|
static make(user: User, time_: number | null | undefined, device_id: string) {
|
||||||
|
const time = (time_ || Date.now())
|
||||||
return this.encode({
|
return this.encode({
|
||||||
author: user.bean.id,
|
author: user.bean.id,
|
||||||
auth: this.makeAuth(user),
|
auth: this.makeAuth(user),
|
||||||
made_time: time,
|
made_time: time,
|
||||||
expired_time: time + (1 * 1000 * 60 * 60 * 24),
|
expired_time: time + (1 * 1000 * 60 * 60 * 24),
|
||||||
|
device_id: device_id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 獲取新令牌
|
||||||
|
* 注意: 只驗證用戶, 不驗證令牌有效性!
|
||||||
|
*/
|
||||||
static makeNewer(user: User, token: string) {
|
static makeNewer(user: User, token: string) {
|
||||||
if (this.check(user, token))
|
if (this.check(user, token))
|
||||||
return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24))
|
return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24), this.decode(token).device_id)
|
||||||
}
|
}
|
||||||
static check(user: User, token: string) {
|
static check(user: User, token: string) {
|
||||||
const tk = this.decode(token)
|
const tk = this.decode(token)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer"
|
||||||
import User from "../data/User.ts";
|
import User from "../data/User.ts"
|
||||||
import BaseApi from "./BaseApi.ts"
|
import BaseApi from "./BaseApi.ts"
|
||||||
import TokenManager from "./TokenManager.ts";
|
import TokenManager from "./TokenManager.ts"
|
||||||
import ChatPrivate from "../data/ChatPrivate.ts";
|
import ChatPrivate from "../data/ChatPrivate.ts"
|
||||||
import Chat from "../data/Chat.ts";
|
import Chat from "../data/Chat.ts"
|
||||||
|
import chalk from "chalk"
|
||||||
|
import ApiManager from "./ApiManager.ts"
|
||||||
|
|
||||||
export default class UserApi extends BaseApi {
|
export default class UserApi extends BaseApi {
|
||||||
override getName(): string {
|
override getName(): string {
|
||||||
@@ -11,11 +13,12 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
override onInit(): void {
|
override onInit(): void {
|
||||||
// 驗證
|
// 驗證
|
||||||
this.registerEvent("User.auth", (args) => {
|
this.registerEvent("User.auth", (args, clientInfo) => {
|
||||||
if (this.checkArgsMissing(args, ['access_token'])) return {
|
if (this.checkArgsMissing(args, ['access_token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
const { deviceId, ip, socket } = clientInfo
|
||||||
try {
|
try {
|
||||||
const access_token = TokenManager.decode(args.access_token as string)
|
const access_token = TokenManager.decode(args.access_token as string)
|
||||||
|
|
||||||
@@ -23,11 +26,21 @@ export default class UserApi extends BaseApi {
|
|||||||
msg: "登錄令牌失效",
|
msg: "登錄令牌失效",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!User.findById(access_token.author)) return {
|
if (!User.findById(access_token.author)) return {
|
||||||
msg: "賬號不存在",
|
msg: "賬號不存在",
|
||||||
code: 401,
|
code: 401,
|
||||||
}
|
}
|
||||||
|
if (access_token.device_id != deviceId) return {
|
||||||
|
msg: "驗證失敗",
|
||||||
|
code: 401,
|
||||||
|
}
|
||||||
|
|
||||||
|
clientInfo.userId = access_token.author
|
||||||
|
console.log(chalk.green('[驗]') + ` ${access_token.author} authed on Client ${deviceId} (ip = ${ip})`)
|
||||||
|
if (ApiManager.clients[clientInfo.userId] == null) ApiManager.clients[clientInfo.userId] = {
|
||||||
|
[deviceId]: socket
|
||||||
|
}
|
||||||
|
else ApiManager.clients[clientInfo.userId][deviceId] = socket
|
||||||
|
|
||||||
return {
|
return {
|
||||||
msg: "成功",
|
msg: "成功",
|
||||||
@@ -45,7 +58,7 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 登錄
|
// 登錄
|
||||||
this.registerEvent("User.login", (args) => {
|
this.registerEvent("User.login", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -65,7 +78,7 @@ export default class UserApi extends BaseApi {
|
|||||||
msg: "成功",
|
msg: "成功",
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
access_token: TokenManager.make(user)
|
access_token: TokenManager.make(user, null, deviceId)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +88,7 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 注冊
|
// 注冊
|
||||||
this.registerEvent("User.register", (args) => {
|
this.registerEvent("User.register", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['nickname', 'password'])) return {
|
if (this.checkArgsMissing(args, ['nickname', 'password'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -105,7 +118,7 @@ export default class UserApi extends BaseApi {
|
|||||||
* ================================================
|
* ================================================
|
||||||
*/
|
*/
|
||||||
// 更新頭像
|
// 更新頭像
|
||||||
this.registerEvent("User.setAvatar", (args) => {
|
this.registerEvent("User.setAvatar", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['avatar', 'token'])) return {
|
if (this.checkArgsMissing(args, ['avatar', 'token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -115,7 +128,7 @@ export default class UserApi extends BaseApi {
|
|||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -130,14 +143,14 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 更新資料
|
// 更新資料
|
||||||
this.registerEvent("User.updateProfile", (args) => {
|
this.registerEvent("User.updateProfile", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -154,14 +167,14 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 獲取用戶信息
|
// 獲取用戶信息
|
||||||
this.registerEvent("User.getMyInfo", (args) => {
|
this.registerEvent("User.getMyInfo", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -180,14 +193,14 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 獲取聯絡人列表
|
// 獲取聯絡人列表
|
||||||
this.registerEvent("User.getMyContacts", (args) => {
|
this.registerEvent("User.getMyContacts", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token'])) return {
|
if (this.checkArgsMissing(args, ['token'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -212,14 +225,14 @@ export default class UserApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 添加聯絡人
|
// 添加聯絡人
|
||||||
this.registerEvent("User.addContact", (args) => {
|
this.registerEvent("User.addContact", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'contact_chat_id'])) return {
|
if (this.checkArgsMissing(args, ['token', 'contact_chat_id'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = TokenManager.decode(args.token as string)
|
const token = TokenManager.decode(args.token as string)
|
||||||
if (!this.checkToken(token)) return {
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
code: 401,
|
code: 401,
|
||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
@@ -237,6 +250,35 @@ export default class UserApi extends BaseApi {
|
|||||||
* 公開資料
|
* 公開資料
|
||||||
* ================================================
|
* ================================================
|
||||||
*/
|
*/
|
||||||
|
// 獲取用戶信息
|
||||||
|
this.registerEvent("User.getInfo", (args, { deviceId }) => {
|
||||||
|
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
||||||
|
msg: "參數缺失",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = TokenManager.decode(args.token as string)
|
||||||
|
if (!this.checkToken(token, deviceId)) return {
|
||||||
|
code: 401,
|
||||||
|
msg: "令牌無效",
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = User.findById(args.target as string)
|
||||||
|
if (user == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "用戶不存在",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
msg: "成功",
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
username: user!.getUserName(),
|
||||||
|
nickname: user!.getNickName(),
|
||||||
|
avatar: user!.getAvatarFileHash() ? "uploaded_files/" + user!.getAvatarFileHash() : null,
|
||||||
|
id: token.author,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,12 +27,12 @@ export default class MessagesManager {
|
|||||||
CREATE TABLE IF NOT EXISTS ${this.getTableName()} (
|
CREATE TABLE IF NOT EXISTS ${this.getTableName()} (
|
||||||
/* 序号, MessageId */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号, MessageId */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
/* 消息文本 */ text TEXT NOT NULL,
|
/* 消息文本 */ text TEXT NOT NULL,
|
||||||
/* 发送者 */ user_id TEXT NOT NULL,
|
/* 发送者 */ user_id TEXT NOT NULL
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
protected getTableName() {
|
protected getTableName() {
|
||||||
return `messages_${this.chat.bean.id}`
|
return `messages_${this.chat.bean.id}`.replaceAll('-', '_')
|
||||||
}
|
}
|
||||||
addMessage({
|
addMessage({
|
||||||
text,
|
text,
|
||||||
@@ -41,13 +41,13 @@ export default class MessagesManager {
|
|||||||
text: string,
|
text: string,
|
||||||
user_id?: string
|
user_id?: string
|
||||||
}) {
|
}) {
|
||||||
MessagesManager.database.prepare(`INSERT INTO ${this.getTableName()} (
|
return MessagesManager.database.prepare(`INSERT INTO ${this.getTableName()} (
|
||||||
text,
|
text,
|
||||||
user_id
|
user_id
|
||||||
) VALUES (?, ?);`).run(
|
) VALUES (?, ?);`).run(
|
||||||
text,
|
text,
|
||||||
user_id || null
|
user_id || null
|
||||||
)
|
).lastInsertRowid
|
||||||
}
|
}
|
||||||
addSystemMessage(text: string) {
|
addSystemMessage(text: string) {
|
||||||
this.addMessage({
|
this.addMessage({
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import ApiCallbackMessage from "../api/ApiCallbackMessage.ts"
|
import ApiCallbackMessage from "../api/ApiCallbackMessage.ts"
|
||||||
|
import * as SocketIo from "socket.io"
|
||||||
|
|
||||||
type EventCallbackFunction = (args: { [key: string]: unknown }) => ApiCallbackMessage
|
type EventCallbackFunction = (args: { [key: string]: unknown }, clientInfo: {
|
||||||
|
userId: string
|
||||||
|
deviceId: string
|
||||||
|
ip: string
|
||||||
|
socket: SocketIo.Socket<SocketIo.DefaultEventsMap, SocketIo.DefaultEventsMap, SocketIo.DefaultEventsMap, any>
|
||||||
|
}) => ApiCallbackMessage
|
||||||
|
|
||||||
export default EventCallbackFunction
|
export default EventCallbackFunction
|
||||||
|
|||||||
Reference in New Issue
Block a user