add
This commit is contained in:
parent
1c3b7bdad0
commit
dcd38d453d
|
@ -1,13 +1,22 @@
|
|||
import { createBunWebSocket } from 'hono/bun';
|
||||
import type { ServerWebSocket } from 'bun';
|
||||
import { WSContext } from 'hono/ws';
|
||||
import { MessageType } from '../../../packages/client/src/websocket/type';
|
||||
|
||||
enum MessageType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
FILE = 'file',
|
||||
RETRACT = 'retract', // 用于实现撤回
|
||||
}
|
||||
interface WSMessageParams {
|
||||
text?: string;
|
||||
type?: MessageType;
|
||||
fileUri?: string;
|
||||
}
|
||||
// 定义消息类型接口
|
||||
interface WSMessage {
|
||||
type: 'join' | 'leave' | 'message';
|
||||
roomId: string;
|
||||
data: any;
|
||||
data: WSMessageParams;
|
||||
}
|
||||
|
||||
// 创建 WebSocket 实例并指定泛型类型
|
||||
|
@ -27,7 +36,7 @@ const wsHandler = upgradeWebSocket((c) => {
|
|||
},
|
||||
onMessage(message, ws) {
|
||||
try {
|
||||
const parsedMessage: WSMessage = JSON.parse(message.data);
|
||||
const parsedMessage: WSMessage = JSON.parse(message.data as any);
|
||||
console.log('收到消息:', parsedMessage);
|
||||
|
||||
switch (parsedMessage.type) {
|
||||
|
|
|
@ -1,161 +1,28 @@
|
|||
'use client';
|
||||
import { useHello, useTRPC, useWebSocket, MessageType } from '@repo/client';
|
||||
import { useHello, useTRPC } from '@repo/client';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRef, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Home() {
|
||||
const trpc = useTRPC();
|
||||
const { data, isLoading } = useQuery(trpc.user.getUser.queryOptions());
|
||||
const [message, setMessage] = useState('');
|
||||
const [roomId, setRoomId] = useState('');
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 使用 WebSocket hook
|
||||
const { messages, currentRoom, connecting, joinRoom, leaveRoom, sendMessage } = useWebSocket();
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
setTimeout(scrollToBottom, 100);
|
||||
};
|
||||
|
||||
const handleJoinRoom = async () => {
|
||||
const success = await joinRoom(roomId.trim());
|
||||
if (success) {
|
||||
setRoomId('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeaveRoom = async () => {
|
||||
await leaveRoom();
|
||||
};
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
const success = await sendMessage({
|
||||
text: message,
|
||||
type: MessageType.TEXT,
|
||||
});
|
||||
if (success) {
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理按回车发送消息
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">WebSocket 房间测试</h1>
|
||||
|
||||
{/* 房间管理 */}
|
||||
<div className="mb-6 p-4 border rounded">
|
||||
<h2 className="text-xl mb-2">房间管理</h2>
|
||||
{!currentRoom ? (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={roomId}
|
||||
onChange={(e) => setRoomId(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={connecting}
|
||||
className="border border-gray-300 rounded px-3 py-2 flex-1"
|
||||
placeholder="输入房间ID..."
|
||||
/>
|
||||
<button
|
||||
onClick={handleJoinRoom}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-green-500 hover:bg-green-600'
|
||||
}`}
|
||||
>
|
||||
{connecting ? '连接中...' : '加入房间'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>当前房间: {currentRoom}</span>
|
||||
<button
|
||||
onClick={handleLeaveRoom}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-red-500 hover:bg-red-600'
|
||||
}`}
|
||||
>
|
||||
离开房间
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-2xl font-bold mb-4">首页</h1>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 border rounded">
|
||||
<h2 className="text-xl mb-2">功能导航</h2>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<Link href="/websocket" className="text-blue-500 hover:text-blue-600 hover:underline">
|
||||
WebSocket 聊天室
|
||||
</Link>
|
||||
</li>
|
||||
{/* 可以在这里添加更多功能链接 */}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 消息显示区域 */}
|
||||
{currentRoom && (
|
||||
<div className="mb-4">
|
||||
<div className="border rounded p-4 h-[400px] overflow-y-auto mb-4 bg-gray-50">
|
||||
{messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`mb-2 p-2 rounded ${
|
||||
msg.action === 'system'
|
||||
? 'bg-gray-200 text-gray-700'
|
||||
: msg.action === 'error'
|
||||
? 'bg-red-100 text-red-700'
|
||||
: 'bg-blue-100 text-blue-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{msg.data.type === MessageType.IMAGE && msg.data.fileUri && (
|
||||
<img src={msg.data.fileUri} alt="图片消息" className="max-w-xs" />
|
||||
)}
|
||||
{msg.data.type === MessageType.FILE && msg.data.fileUri && (
|
||||
<a
|
||||
href={msg.data.fileUri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
下载文件
|
||||
</a>
|
||||
)}
|
||||
{msg.data.text && <div>{msg.data.text}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={connecting}
|
||||
className="border border-gray-300 rounded px-3 py-2 flex-1"
|
||||
placeholder="输入消息..."
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-500 hover:bg-blue-600'
|
||||
}`}
|
||||
>
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!currentRoom && (
|
||||
<div>
|
||||
<p className="text-gray-600">提示: 请先加入一个房间开始聊天</p>
|
||||
<p className="text-gray-600">打开多个浏览器窗口,输入相同的房间ID来测试房间内通信</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
'use client';
|
||||
import { useHello, useTRPC, useWebSocket, MessageType } from '@repo/client';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
export default function WebSocketPage() {
|
||||
const trpc = useTRPC();
|
||||
const { data, isLoading } = useQuery(trpc.user.getUser.queryOptions());
|
||||
const [message, setMessage] = useState('');
|
||||
const [roomId, setRoomId] = useState('');
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 使用 WebSocket hook
|
||||
const { messages, currentRoom, connecting, joinRoom, leaveRoom, sendMessage } = useWebSocket();
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
setTimeout(scrollToBottom, 100);
|
||||
};
|
||||
|
||||
const handleJoinRoom = async () => {
|
||||
const success = await joinRoom(roomId.trim());
|
||||
if (success) {
|
||||
setRoomId('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeaveRoom = async () => {
|
||||
await leaveRoom();
|
||||
};
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
const success = await sendMessage({
|
||||
text: message,
|
||||
type: MessageType.TEXT,
|
||||
});
|
||||
if (success) {
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理按回车发送消息
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">WebSocket 房间测试</h1>
|
||||
|
||||
{/* 房间管理 */}
|
||||
<div className="mb-6 p-4 border rounded">
|
||||
<h2 className="text-xl mb-2">房间管理</h2>
|
||||
{!currentRoom ? (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={roomId}
|
||||
onChange={(e) => setRoomId(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={connecting}
|
||||
className="border border-gray-300 rounded px-3 py-2 flex-1"
|
||||
placeholder="输入房间ID..."
|
||||
/>
|
||||
<button
|
||||
onClick={handleJoinRoom}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-green-500 hover:bg-green-600'
|
||||
}`}
|
||||
>
|
||||
{connecting ? '连接中...' : '加入房间'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>当前房间: {currentRoom}</span>
|
||||
<button
|
||||
onClick={handleLeaveRoom}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-red-500 hover:bg-red-600'
|
||||
}`}
|
||||
>
|
||||
离开房间
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 消息显示区域 */}
|
||||
{currentRoom && (
|
||||
<div className="mb-4">
|
||||
<div className="border rounded p-4 h-[400px] overflow-y-auto mb-4 bg-gray-50">
|
||||
{messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`mb-2 p-2 rounded ${
|
||||
msg.action === 'system'
|
||||
? 'bg-gray-200 text-gray-700'
|
||||
: msg.action === 'error'
|
||||
? 'bg-red-100 text-red-700'
|
||||
: 'bg-blue-100 text-blue-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{msg.data.type === MessageType.IMAGE && msg.data.fileUri && (
|
||||
<img src={msg.data.fileUri} alt="图片消息" className="max-w-xs" />
|
||||
)}
|
||||
{msg.data.type === MessageType.FILE && msg.data.fileUri && (
|
||||
<a
|
||||
href={msg.data.fileUri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
下载文件
|
||||
</a>
|
||||
)}
|
||||
{msg.data.text && <div>{msg.data.text}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={connecting}
|
||||
className="border border-gray-300 rounded px-3 py-2 flex-1"
|
||||
placeholder="输入消息..."
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
disabled={connecting}
|
||||
className={`px-4 py-2 rounded text-white ${
|
||||
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-500 hover:bg-blue-600'
|
||||
}`}
|
||||
>
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!currentRoom && (
|
||||
<div>
|
||||
<p className="text-gray-600">提示: 请先加入一个房间开始聊天</p>
|
||||
<p className="text-gray-600">打开多个浏览器窗口,输入相同的房间ID来测试房间内通信</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue