From 3fbbd8585760de47cb76413a1836b9d7cc8ec9b9 Mon Sep 17 00:00:00 2001 From: lee Date: Thu, 12 Jun 2025 20:48:51 +0800 Subject: [PATCH] socketzustand --- apps/talk/package.json | 2 +- apps/talk/src/app.module.ts | 4 +- .../config/guards/wsJwtGuard.ts} | 0 apps/talk/src/modules/chat/chat.module.ts | 36 +++++ .../src/modules/chat/gateway/chat.gateway.ts | 134 ++++++++++++++++++ apps/talk/src/modules/chat/type.ts | 12 ++ apps/talk/src/modules/group/dtos/group.dto.ts | 9 ++ .../modules/group/entities/group.entity.ts | 38 +++++ .../group/entities/groupMessage.entity.ts | 22 +++ apps/talk/src/modules/group/group.module.ts | 15 ++ apps/talk/src/modules/group/group.service.ts | 69 +++++++++ .../src/modules/user/services/auth.service.ts | 10 +- .../modules/user/services/token.service.ts | 4 +- apps/talk/src/modules/user/user.module.ts | 3 +- apps/talk/src/websocket/events.gateway.ts | 61 -------- apps/web/.env | 3 +- apps/web/src/app/(pages)/chat/page.tsx | 86 +++++------ apps/web/src/app/(pages)/test/page.tsx | 44 +++++- .../appProvider/stocketProvider.tsx | 29 ++++ .../theme/theme-provider.tsx | 0 apps/web/src/app/_components/chat/Talk.tsx | 75 ++++++++++ .../app/_components/chat/onLineUserList.tsx | 69 +++++++++ .../web/src/app/_components/chat/talkStore.ts | 0 apps/web/src/app/_components/header/index.tsx | 8 +- apps/web/src/app/_components/ui/button.tsx | 29 ++-- apps/web/src/app/layout.tsx | 14 +- apps/web/src/lib/config.ts | 1 + apps/web/src/lib/socket.ts | 96 ++++++++++++- package.json | 3 +- turbo.json | 15 +- 30 files changed, 739 insertions(+), 152 deletions(-) rename apps/talk/src/{test.ts => common/config/guards/wsJwtGuard.ts} (100%) create mode 100644 apps/talk/src/modules/chat/chat.module.ts create mode 100644 apps/talk/src/modules/chat/gateway/chat.gateway.ts create mode 100644 apps/talk/src/modules/chat/type.ts create mode 100644 apps/talk/src/modules/group/dtos/group.dto.ts create mode 100644 apps/talk/src/modules/group/entities/group.entity.ts create mode 100644 apps/talk/src/modules/group/entities/groupMessage.entity.ts create mode 100644 apps/talk/src/modules/group/group.module.ts create mode 100644 apps/talk/src/modules/group/group.service.ts delete mode 100644 apps/talk/src/websocket/events.gateway.ts create mode 100644 apps/web/src/app/_components/appProvider/stocketProvider.tsx rename apps/web/src/app/_components/{ => appProvider}/theme/theme-provider.tsx (100%) create mode 100644 apps/web/src/app/_components/chat/Talk.tsx create mode 100644 apps/web/src/app/_components/chat/onLineUserList.tsx create mode 100644 apps/web/src/app/_components/chat/talkStore.ts create mode 100644 apps/web/src/lib/config.ts diff --git a/apps/talk/package.json b/apps/talk/package.json index 57d2813..1801373 100644 --- a/apps/talk/package.json +++ b/apps/talk/package.json @@ -9,7 +9,7 @@ "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", - "start:dev": "cross-env NODE_ENV=dev nest start --watch", + "dev": "cross-env NODE_ENV=dev nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", diff --git a/apps/talk/src/app.module.ts b/apps/talk/src/app.module.ts index a60d893..61ff170 100644 --- a/apps/talk/src/app.module.ts +++ b/apps/talk/src/app.module.ts @@ -3,12 +3,12 @@ import { APP_PIPE } from "@nestjs/core"; import { ConfigModule } from "./common/config/config.module"; import { database } from "./config/database.config"; +import { ChatModule } from "./modules/chat/chat.module"; import { Configure } from "./modules/config/configure"; import { CoreModule } from "./modules/core/core.module"; import { DatabaseModule } from "./modules/database/database.module"; import { FileMOdule } from "./modules/file/file.module"; import { UserModule } from "./modules/user/user.module"; -import { EventsGateway } from "./websocket/events.gateway"; // 当providers 为空时,就会从di容器从import的模块中查找->其他模块需要两个部分一个是providers,一个是exports,providers是用来提供实例的,exports是用来导出模块的 // 第二种直接在当前providers中导入其他模块的providers,然后在当前模块的providers中导入其他模块的exports,这样就可以实现模块间的依赖注入 @@ -24,6 +24,7 @@ export class AppModule { ConfigModule.forRoot(), CoreModule.forRoot(configure), FileMOdule.forRoot(), + ChatModule.forRoot(configure), ], controllers: [], exports: [ @@ -39,7 +40,6 @@ export class AppModule { provide: APP_PIPE, useValue: new ValidationPipe({ transform: true, whitelist: true }), }, - EventsGateway, ], global: true, }; diff --git a/apps/talk/src/test.ts b/apps/talk/src/common/config/guards/wsJwtGuard.ts similarity index 100% rename from apps/talk/src/test.ts rename to apps/talk/src/common/config/guards/wsJwtGuard.ts diff --git a/apps/talk/src/modules/chat/chat.module.ts b/apps/talk/src/modules/chat/chat.module.ts new file mode 100644 index 0000000..c2a5237 --- /dev/null +++ b/apps/talk/src/modules/chat/chat.module.ts @@ -0,0 +1,36 @@ +import { DynamicModule, Module } from "@nestjs/common"; +import { JwtModule, JwtService } from "@nestjs/jwt"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { Configure } from "../config/configure"; +import { AUTH_JWT_SECRET } from "../user/constants"; +import * as entities from "../user/entities"; +import { AuthService, TokenService, UserService } from "../user/services"; +import { UserModule } from "../user/user.module"; +import { ChatGateway } from "./gateway/chat.gateway"; + +@Module({}) +export class ChatModule { + static forRoot(configure: Configure): DynamicModule { + const jwtSecret = configure.env().get(AUTH_JWT_SECRET, "11"); + return { + module: ChatModule, + imports: [ + JwtModule.register({ + secret: jwtSecret, + }), + UserModule.forRoot(configure), + TypeOrmModule.forFeature(Object.values(entities), "default"), + ], + providers: [ + ChatGateway, + UserService, + JwtService, + AuthService, + TokenService, + { provide: AUTH_JWT_SECRET, useValue: jwtSecret }, + ], + exports: [], + }; + } +} diff --git a/apps/talk/src/modules/chat/gateway/chat.gateway.ts b/apps/talk/src/modules/chat/gateway/chat.gateway.ts new file mode 100644 index 0000000..17f61b2 --- /dev/null +++ b/apps/talk/src/modules/chat/gateway/chat.gateway.ts @@ -0,0 +1,134 @@ +import { + ConnectedSocket, + MessageBody, + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, +} from "@nestjs/websockets"; +import { Server, Socket } from "socket.io"; +import { TokenService } from "src/modules/user/services/token.service"; + +import { RCode } from "../type"; + +// 好友消息 +interface FriendMessageDto { + _id: number; + userId: string; + friendId: string; + content: string; + messageType: string; + time: number; +} + +interface UserPayload { + id: string; + username: string; + role: string; + exp: number; + iat: number; +} +@WebSocketGateway({ + cors: { + origin: "*", + }, +}) +export class ChatGateway + implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect +{ + constructor(private readonly tokenService: TokenService) {} + @WebSocketServer() server: Server; + + async handleConnection(client: Socket, ..._args: any[]) { + const token = client.handshake.query.token; + if (token && typeof token === "string") { + const user: UserPayload = + await this.tokenService.verifyAccessToken(token); + if (user) { + const userId = user.id; + // 用户独有消息房间 根据userId + client.join(userId); + console.log(`User ${userId} connected`); + client.broadcast.emit("userOnline", { + code: RCode.OK, + msg: "User online", + data: userId, + }); + return "连接成功"; + } + } + } + handleDisconnect(client: Socket) { + const userId = client.handshake.query.userId; + console.log("用户下线", userId); + // 下线提醒广播给所有人 + client.broadcast.emit("userOffline", { + code: RCode.OK, + msg: "userOffline", + data: userId, + }); + } + afterInit(_server: Server) { + console.log("Init"); + } + + // 加入私聊的socket连接 + @SubscribeMessage("joinFriendSocket") + joinFriendSocket( + @ConnectedSocket() client: Socket, + @MessageBody() data: { friendId: string; userId: string }, + ) { + if (data.friendId && data.userId) { + const ids: string[] = [data.friendId, data.userId]; + const rommid = ids.sort((a, b) => a.localeCompare(b)).join(""); + client.join(rommid); + this.server.to(data.userId).emit("joinFriendSocket", { + code: RCode.OK, + msg: "进入私聊socket成功", + data, + }); + } + } + // 发送私聊消息 + @SubscribeMessage("friendMessage") + friendMessage( + @ConnectedSocket() client: Socket, + @MessageBody() data: FriendMessageDto, + ) { + if (data.userId && data.friendId) { + const ids: string[] = [data.friendId, data.userId]; + const rommid = ids.sort((a, b) => a.localeCompare(b)).join(""); + client.join(rommid); + data.time = new Date().valueOf(); + this.server.to(rommid).emit("friendMessage", { + code: RCode.OK, + msg: "发送私聊消息成功", + data, + }); + } + } + @SubscribeMessage("chatData") + getAllData(@MessageBody() token: string) { + console.log(token,"chatData"); + + const user: UserPayload = this.tokenService.verifyAccessToken(token); + if (user) { + const onLineUserIds = Array.from( + new Set( + Object.values(this.server.sockets.sockets).map( + (socket) => socket.request._query.userId, + ), + ), + ); + this.server.to(user.id).emit("chatData", { + code: RCode.OK, + msg: "获取聊天数据成功", + data: { + onLineUserIds, + }, + }); + } + } +} diff --git a/apps/talk/src/modules/chat/type.ts b/apps/talk/src/modules/chat/type.ts new file mode 100644 index 0000000..0daa524 --- /dev/null +++ b/apps/talk/src/modules/chat/type.ts @@ -0,0 +1,12 @@ +export interface UserPayload { + id: string; + username: string; + role: string; + exp: number; + iat: number; +} +export enum RCode { + OK, + FAIL, + ERROR, +} diff --git a/apps/talk/src/modules/group/dtos/group.dto.ts b/apps/talk/src/modules/group/dtos/group.dto.ts new file mode 100644 index 0000000..41ad334 --- /dev/null +++ b/apps/talk/src/modules/group/dtos/group.dto.ts @@ -0,0 +1,9 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class GetGroupMessageDto { + userId: string; + groupId: string; + current: number; + pageSize: number; +} diff --git a/apps/talk/src/modules/group/entities/group.entity.ts b/apps/talk/src/modules/group/entities/group.entity.ts new file mode 100644 index 0000000..d4c8e11 --- /dev/null +++ b/apps/talk/src/modules/group/entities/group.entity.ts @@ -0,0 +1,38 @@ +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() +export class GroupEntity { + @PrimaryGeneratedColumn("uuid") + groupId: string; + + @Column() + userId: string; + + @Column() + groupName: string; + + @Column({ default: "群主很懒,没写公告" }) + notice: string; + + @Column({ type: "double", default: new Date().valueOf() }) + createTime: number; +} + +@Entity() +export class GroupMapEntity { + @PrimaryGeneratedColumn() + _id: number; + + @Column() + groupId: string; + + @Column() + userId: string; + + @Column({ + type: "double", + default: new Date().valueOf(), + comment: "进群时间", + }) + createTime: number; +} diff --git a/apps/talk/src/modules/group/entities/groupMessage.entity.ts b/apps/talk/src/modules/group/entities/groupMessage.entity.ts new file mode 100644 index 0000000..01b1a8e --- /dev/null +++ b/apps/talk/src/modules/group/entities/groupMessage.entity.ts @@ -0,0 +1,22 @@ +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() +export class GroupMessageEntity { + @PrimaryGeneratedColumn() + _id: number; + + @Column() + userId: string; + + @Column() + groupId: string; + + @Column() + content: string; + + @Column() + messageType: string; + + @Column("double") + time: number; +} diff --git a/apps/talk/src/modules/group/group.module.ts b/apps/talk/src/modules/group/group.module.ts new file mode 100644 index 0000000..6352f8a --- /dev/null +++ b/apps/talk/src/modules/group/group.module.ts @@ -0,0 +1,15 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { GroupEntity, GroupMapEntity } from "./entities/group.entity"; +import { GroupMessageEntity } from "./entities/groupMessage.entity"; + +@Module({ + imports: [ + TypeOrmModule.forFeature([GroupEntity, GroupMapEntity, GroupMessageEntity]), + ], + providers: [], + controllers: [], + exports: [], +}) +export class GroupModule {} diff --git a/apps/talk/src/modules/group/group.service.ts b/apps/talk/src/modules/group/group.service.ts new file mode 100644 index 0000000..af93965 --- /dev/null +++ b/apps/talk/src/modules/group/group.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { In, Repository } from "typeorm"; + +import { RCode } from "../chat/type"; +import { GetGroupMessageDto } from "./dtos/group.dto"; +import { GroupEntity, GroupMapEntity } from "./entities/group.entity"; +import { GroupMessageEntity } from "./entities/groupMessage.entity"; + +@Injectable() +export class GroupService { + constructor( + @InjectRepository(GroupEntity) + private readonly groupRepository: Repository, + @InjectRepository(GroupMapEntity) + private readonly groupMapRepository: Repository, + @InjectRepository(GroupMessageEntity) + private readonly groupMessageRepository: Repository, + ) {} + async postGroups(groupIds: string[]) { + if (!groupIds || groupIds.length === 0) { + return { code: RCode.ERROR, data: [], msg: "群组ID不能为空" }; + } + try { + const groups = await this.groupRepository.find({ + where: { groupId: In(groupIds) }, + }); + return { code: RCode.OK, data: groups, msg: "获取群信息成功" }; + } catch (e) { + return { code: RCode.ERROR, data: [], msg: "获取群信息失败" }; + } + } + async getUserGroups(userId: string) { + try { + if (!userId) { + const data = await this.groupMapRepository.find(); + return { code: RCode.ERROR, data, msg: "用户ID不能为空" }; + } + const data = await this.groupMapRepository.find({ + where: { userId }, + }); + return { code: RCode.OK, data, msg: "获取用户群信息成功" }; + } catch (e) { + return { code: RCode.ERROR, data: [], msg: "获取用户群信息失败" }; + } + } + async getGroupMessages(value: GetGroupMessageDto) { + const { userId, groupId, current, pageSize } = value; + const groupUser = await this.groupMapRepository + .findOne({ + where: { userId, groupId }, + }) + .catch((_) => null); + if (!groupUser) { + return { code: RCode.ERROR, data: [], msg: "用户不在该群组中" }; + } + const { createTime } = groupUser; + const groupMessage = await this.groupMessageRepository + .createQueryBuilder("groupMessage") + .where("groupMessage.groupId = :groupId", { groupId }) + .andWhere("groupMessage.time >= :createTime", { + createTime: createTime - 86400000, + }) + .skip(current) + .take(pageSize) + .orderBy("groupMessage.time", "ASC") + .getMany(); + } +} diff --git a/apps/talk/src/modules/user/services/auth.service.ts b/apps/talk/src/modules/user/services/auth.service.ts index 9900236..a7d8c2f 100644 --- a/apps/talk/src/modules/user/services/auth.service.ts +++ b/apps/talk/src/modules/user/services/auth.service.ts @@ -1,8 +1,11 @@ -import { Injectable } from "@nestjs/common"; +import { Inject, Injectable } from "@nestjs/common"; +import { JwtService } from "@nestjs/jwt"; import { InjectRepository } from "@nestjs/typeorm"; import { EntityNotFoundError, Repository } from "typeorm"; +import { AUTH_JWT_SECRET } from "../constants"; import { CreateUserDto } from "../dtos/user.dto"; +import { AccessTokenEntity } from "../entities/access-token.entity"; import { UserEntity } from "../entities/user.entity"; import { decrypt } from "../helpers"; import { TokenService } from "./token.service"; @@ -11,10 +14,15 @@ import { UserService } from "./user.service"; @Injectable() export class AuthService { constructor( + protected jwtService: JwtService, private userService: UserService, private tokenService: TokenService, @InjectRepository(UserEntity) protected userRepository: Repository, + @Inject(AUTH_JWT_SECRET) + protected access_secret: string, + @InjectRepository(AccessTokenEntity) + protected accesTokenRepository: Repository, ) {} async validateUser(credential: string, password: string) { const user = await this.userService.findOneByCredential(credential); diff --git a/apps/talk/src/modules/user/services/token.service.ts b/apps/talk/src/modules/user/services/token.service.ts index 78b0cc4..9bfc8c5 100644 --- a/apps/talk/src/modules/user/services/token.service.ts +++ b/apps/talk/src/modules/user/services/token.service.ts @@ -100,8 +100,8 @@ export class TokenService { }); accessToken && (await accessToken.remove()); } - async verifyAccessToken(token: string) { - const user = await this.jwtService.verify(token, { + verifyAccessToken(token: string) { + const user = this.jwtService.verify(token, { secret: this.access_secret, }); return user || false; diff --git a/apps/talk/src/modules/user/user.module.ts b/apps/talk/src/modules/user/user.module.ts index 7bf948a..7d76892 100644 --- a/apps/talk/src/modules/user/user.module.ts +++ b/apps/talk/src/modules/user/user.module.ts @@ -22,7 +22,6 @@ import { UserSubscriber } from "./subscribers/user.subscriber"; export class UserModule { static forRoot(configure: Configure) { const jwtSecret = configure.env().get(AUTH_JWT_SECRET, "11"); - console.log(jwtSecret); const smtpconfig = smtp(configure); if (!jwtSecret) { throw new Error("AUTH_JWT_SECRET is not set"); @@ -50,7 +49,7 @@ export class UserModule { { provide: "smtpConfig", useValue: smtpconfig }, ], controllers: [UserController, AuthController, AccountController], - exports: [], + exports: [TokenService, UserService, AuthService], }; } } diff --git a/apps/talk/src/websocket/events.gateway.ts b/apps/talk/src/websocket/events.gateway.ts deleted file mode 100644 index 5870e63..0000000 --- a/apps/talk/src/websocket/events.gateway.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - OnGatewayConnection, - OnGatewayDisconnect, - OnGatewayInit, - SubscribeMessage, - WebSocketGateway, - WebSocketServer, -} from "@nestjs/websockets"; -import { Server, Socket } from "socket.io"; - -@WebSocketGateway({ - cors: { - origin: "*", - }, -}) -export class EventsGateway - implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect -{ - users = 0; - @WebSocketServer() server: Server; - - handleConnection(client: Socket, ...args: any[]) { - console.log(client.id, "connected"); - this.users++; - return this.server.emit("users", `当前在线人数:${this.users}`); - } - handleDisconnect(client: Socket) { - console.log(client.id, "disconnected"); - this.users--; - return this.server.emit("users", `当前在线人数:${this.users}`); - } - afterInit(server: Server) { - console.log("Init"); - } - @SubscribeMessage("events") - handleMessage(client: Socket, payload: string): void { - console.log(client.id, "sent message", payload); - - // 获取当前时间并格式化为“YYYY-MM-DD HH:mm:ss” - const currentTime = new Date() - .toLocaleString("zh-CN", { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: false, // 使用24小时制 - }) - .replace(/\//g, "-") - .replace(/,/, " "); // 替换分隔符以符合所需格式 - - // 创建一个新的消息对象,包含时间和消息内容 - const messageWithTime = { - time: currentTime, // 当前时间 - data: payload, - }; - - this.server.emit("msgToClient", messageWithTime); // 发送包含时间的消息对象 - } -} diff --git a/apps/web/.env b/apps/web/.env index cc95856..5832e98 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,3 +1,4 @@ DATABASE_URL="postgresql://postgres:pass123@127.0.0.1:5432/web2?schema=public" NEXT_PUBLIC_APP_URL="http://0.0.0.0:3001" -AUTH_JWT_SECRET="hTVLuGqhuKZW9HUnKzs83yvVBitlwc5d0PNfJqDRsRs=" \ No newline at end of file +AUTH_JWT_SECRET="hTVLuGqhuKZW9HUnKzs83yvVBitlwc5d0PNfJqDRsRs=" +NEXT_PUBLIC_SOCKET_URL="http://127.0.0.1:4000" \ No newline at end of file diff --git a/apps/web/src/app/(pages)/chat/page.tsx b/apps/web/src/app/(pages)/chat/page.tsx index 99c041c..2e3a19f 100644 --- a/apps/web/src/app/(pages)/chat/page.tsx +++ b/apps/web/src/app/(pages)/chat/page.tsx @@ -1,57 +1,45 @@ "use client"; -import { useEffect, useState } from "react"; -import { socket } from "@/lib/socket"; +import { useGetCookies } from "cookies-next"; +import { useEffect } from "react"; + +import { useAuthStore } from "@/app/_components/auth/store"; +import useSocketStory from "@/lib/socket"; export default function ChatPage() { - const [isConnected, setIsConnected] = useState(socket.connected); - const [fooEvents, setFooEvents] = useState([]); - const [msg, setMsg] = useState(""); + const cookies = useGetCookies(); + const token = cookies()?.auth_token || ""; + const { connect, socket,emit } = useSocketStory((state) => state); + const { auth } = useAuthStore((state) => state); + + +useEffect(() => { + if (socket) { + emit("chatData",token) + socket.on("chatData", (data:any) => { + console.log("chatData", data); + }); + // socket 已经可用,可以注册事件或发送消息 + console.log("socket 已连接", socket); + } + return ()=>{ + if (socket) { + socket.off("chatData"); + console.log("socket 已断开", socket); + } + } +}, [socket]); + // 只负责连接 useEffect(() => { - function onConnect() { - setIsConnected(true); - } - function onDisconnect() { - setIsConnected(false); - } - function onFooEvent(value: any) { - console.log(value); + connect({ + token, + user: { userId: auth?.id || "" }, + }); + // 只在 auth/cookies/connect 变化时重新连接 + }, [auth, cookies, connect]); - setFooEvents((previous) => [...previous, value]); - } - socket.on("connect", onConnect); - socket.on("disconnect", onDisconnect); - socket.on("msgToClient", onFooEvent); - return () => { - socket.off("connect", onConnect); - socket.off("disconnect", onDisconnect); - socket.off("msgToClient", onFooEvent); - }; - }, []); - return ( -
-

{isConnected ? "Connected" : "Disconnected"}

- - - setMsg(e.target.value)} - placeholder="Type something" - /> - -
    - 11 - {fooEvents.map((event, index) => ( -
  • {event.data}
  • - ))} -
-
- ); + + + return
Chat Page
; } diff --git a/apps/web/src/app/(pages)/test/page.tsx b/apps/web/src/app/(pages)/test/page.tsx index 5123afd..e7b14d6 100644 --- a/apps/web/src/app/(pages)/test/page.tsx +++ b/apps/web/src/app/(pages)/test/page.tsx @@ -1,3 +1,45 @@ +"use client"; + +import { useState } from "react"; + export default function TestPage() { - return
; + const data = [ + { id: "1", value: "Test 1" }, + { id: "2", value: "Test 2" }, + { id: "3", value: "Test 3" }, + ]; + + const [selectedValue, setSelectedValue] = useState(null); + + return ( +
+
+ +
+
{selectedValue && }
+
+ ); } + +const Test = ({ value }: { value: string }) => { + return
{value}
; +}; + +const Test2 = ({ + data, + onSelect, +}: { + data: { id: string; value: string }[]; + onSelect: (value: string) => void; +}) => { + return ( +
+ {data.map((item) => ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
onSelect(item.value)} key={item.id}> + {item.value} +
+ ))} +
+ ); +}; diff --git a/apps/web/src/app/_components/appProvider/stocketProvider.tsx b/apps/web/src/app/_components/appProvider/stocketProvider.tsx new file mode 100644 index 0000000..02041ce --- /dev/null +++ b/apps/web/src/app/_components/appProvider/stocketProvider.tsx @@ -0,0 +1,29 @@ +"use client"; +import type React from "react"; +import type { PropsWithChildren } from "react"; + +import { useEffect } from "react"; + +import useSocketStory from "@/lib/socket"; + +import { ThemeProvider } from "./theme/theme-provider"; + +export const StocketProvider: React.FC = ({ children }) => { + const disconnect = useSocketStory((state) => state.disconnect); + useEffect(() => { + window.addEventListener("beforeunload", disconnect); + return () => { + window.removeEventListener("beforeunload", disconnect); + }; + }, [disconnect]); + return ( + + {children} + + ); +}; diff --git a/apps/web/src/app/_components/theme/theme-provider.tsx b/apps/web/src/app/_components/appProvider/theme/theme-provider.tsx similarity index 100% rename from apps/web/src/app/_components/theme/theme-provider.tsx rename to apps/web/src/app/_components/appProvider/theme/theme-provider.tsx diff --git a/apps/web/src/app/_components/chat/Talk.tsx b/apps/web/src/app/_components/chat/Talk.tsx new file mode 100644 index 0000000..e35f63f --- /dev/null +++ b/apps/web/src/app/_components/chat/Talk.tsx @@ -0,0 +1,75 @@ +"use client"; +import { useEffect, useState } from "react"; + +import { socket } from "@/lib/socket"; + +import type { User } from "./onLineUserList"; + +import { Button } from "../ui/button"; +import { Textarea } from "../ui/textarea"; + +//event: "msgToClient" 返回值 +interface MessageWithTime { + id: string; + time: string; + massage: string; + user: UserPayload | undefined; +} + +// jwt payload +interface UserPayload { + id: string; + username: string; + role: string; + exp: number; + iat: number; +} + +export const Talk = ({ user }: { user: User }) => { + const [fooEvents, setFooEvents] = useState([]); + const [message, setmessage] = useState(""); + + useEffect(() => { + function onFooEvent(value: any) { + const uniqueId = `${Date.now()}-${Math.random()}`; + setFooEvents((previous) => [...previous, { ...value, id: uniqueId }]); + } + + socket.on("msgToClient", onFooEvent); + return () => { + socket.off("msgToClient", onFooEvent); + }; + }, []); + + return ( +
+

Talk with {user.user.username}

+
+ {fooEvents.map((event) => ( +
+
+

{event.user?.username}

+

{event.time}

+
+ +

{event.massage}

+
+ ))} +
+