test: 单个会话订阅. 发送和接收
parent
7bdd2a6eac
commit
e13bc7b096
@ -0,0 +1,177 @@
|
||||
import {
|
||||
webSocket,
|
||||
} from "rxjs/webSocket";
|
||||
import { filter, buffer, flatMap, merge, map, tap } from "rxjs/operators";
|
||||
// import { v4 as uuid } from "uuid";
|
||||
import { SHA256 } from "crypto-js";
|
||||
|
||||
export class RealTimeAPI {
|
||||
constructor(param) {
|
||||
this.webSocket = webSocket(param);
|
||||
}
|
||||
|
||||
getObservable() {
|
||||
return this.webSocket;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
return this.webSocket.unsubscribe();
|
||||
}
|
||||
|
||||
onMessage(messageHandler) {
|
||||
this.subscribe(messageHandler, undefined, undefined);
|
||||
}
|
||||
|
||||
onError(errorHandler) {
|
||||
this.subscribe(undefined, errorHandler, undefined);
|
||||
}
|
||||
|
||||
onCompletion(completionHandler) {
|
||||
this.subscribe(undefined, undefined, completionHandler);
|
||||
}
|
||||
|
||||
subscribe(messageHandler, errorHandler, completionHandler) {
|
||||
// this.getObservable().subscribe(
|
||||
// messageHandler,
|
||||
// errorHandler,
|
||||
// completionHandler
|
||||
// );
|
||||
this.getObservable().subscribe({
|
||||
next: messageHandler,
|
||||
error: errorHandler,
|
||||
complete: completionHandler
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(messageObject) {
|
||||
this.webSocket.next(messageObject);
|
||||
}
|
||||
|
||||
getObservableFilteredByMessageType(messageType) {
|
||||
return this.getObservable().pipe(
|
||||
filter((message) => message.msg === messageType)
|
||||
);
|
||||
}
|
||||
|
||||
getObservableFilteredByID(id) {
|
||||
return this.getObservable().pipe(
|
||||
filter((message) => message.id === id)
|
||||
);
|
||||
}
|
||||
|
||||
connectToServer() {
|
||||
this.sendMessage({
|
||||
msg: "connect",
|
||||
version: "1",
|
||||
support: ["1", "pre2", "pre1"]
|
||||
});
|
||||
return this.getObservableFilteredByMessageType("connected");
|
||||
}
|
||||
|
||||
keepAlive() {
|
||||
return this.getObservableFilteredByMessageType("ping").pipe(
|
||||
tap(() => this.sendMessage({ msg: "pong" }))
|
||||
);
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
let id = 'uuid()';
|
||||
let usernameType = username.indexOf("@") !== -1 ? "email" : "username";
|
||||
this.sendMessage({
|
||||
msg: "method",
|
||||
method: "login",
|
||||
id: id,
|
||||
params: [
|
||||
{
|
||||
user: { [usernameType]: username },
|
||||
password: {
|
||||
digest: SHA256(password).toString(),
|
||||
algorithm: "sha-256"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
return this.getLoginObservable(id);
|
||||
}
|
||||
|
||||
loginWithAuthToken(authToken) {
|
||||
let id = 'uuid()';
|
||||
this.sendMessage({
|
||||
msg: "method",
|
||||
method: "login",
|
||||
id: id,
|
||||
params: [{ resume: authToken }]
|
||||
});
|
||||
return this.getLoginObservable(id);
|
||||
}
|
||||
|
||||
loginWithOAuth(credToken, credSecret) {
|
||||
let id = 'uuid()';
|
||||
this.sendMessage({
|
||||
msg: "method",
|
||||
method: "login",
|
||||
id: id,
|
||||
params: [
|
||||
{
|
||||
oauth: {
|
||||
credentialToken: credToken,
|
||||
credentialSecret: credSecret
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
return this.getLoginObservable(id);
|
||||
}
|
||||
|
||||
getLoginObservable(id) {
|
||||
let resultObservable = this.getObservableFilteredByID(id);
|
||||
let resultId;
|
||||
|
||||
let addedObservable = this.getObservable().pipe(
|
||||
buffer(
|
||||
resultObservable.pipe(
|
||||
map(({ msg, error, result }) => {
|
||||
if (msg === "result" && !error) return (resultId = result.id);
|
||||
})
|
||||
)
|
||||
),
|
||||
flatMap(x => x),
|
||||
filter(({ id: msgId }) => resultId !== undefined && msgId === resultId),
|
||||
merge(resultObservable)
|
||||
);
|
||||
|
||||
return addedObservable;
|
||||
}
|
||||
|
||||
callMethod(method, ...params) {
|
||||
let id = 'uuid()';
|
||||
this.sendMessage({
|
||||
msg: "method",
|
||||
method,
|
||||
id,
|
||||
params
|
||||
});
|
||||
return this.getObservableFilteredByID(id);
|
||||
}
|
||||
|
||||
getSubscription(streamName, streamParam, addEvent) {
|
||||
let id = 'uuid()';
|
||||
let subscription = this.webSocket.multiplex(
|
||||
() => ({
|
||||
msg: "sub",
|
||||
id: id,
|
||||
name: streamName,
|
||||
params: [streamParam, addEvent]
|
||||
}),
|
||||
() => ({
|
||||
msg: "unsub",
|
||||
id: id
|
||||
}),
|
||||
(message) =>
|
||||
typeof message.collection === "string" &&
|
||||
message.collection === streamName &&
|
||||
message.fields.eventName === streamParam
|
||||
);
|
||||
return subscription;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import { makeAutoObservable, runInAction, toJS } from 'mobx';
|
||||
import { RealTimeAPI } from '@/lib/realTimeAPI';
|
||||
|
||||
const URL = 'ws://202.103.68.144:8888/whatever/';
|
||||
let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' });
|
||||
|
||||
class Conversations {
|
||||
constructor() {
|
||||
makeAutoObservable(this, { rootStore: false });
|
||||
// this.sendMessage = this.sendMessage.bind(this);
|
||||
|
||||
realtimeAPI.onError(this.addError.bind(this, 'Error'));
|
||||
realtimeAPI.onMessage(this.handleMessage.bind(this));
|
||||
realtimeAPI.onCompletion(this.addError.bind(this, 'Not Connected to Server'));
|
||||
realtimeAPI.keepAlive(); // Ping Server
|
||||
// realtimeAPI.onMessage().subscribe(message => {
|
||||
// this.addMessage(message);
|
||||
// });
|
||||
}
|
||||
|
||||
addError = (reason) => {
|
||||
// this.errors.push({ reason });
|
||||
this.errors = [...this.errors, { reason }];
|
||||
}
|
||||
|
||||
addMessage = (message) => {
|
||||
// this.messages.push(msg.message); // Mobx will not work
|
||||
this.messages = [...this.messages, message];
|
||||
}
|
||||
|
||||
handleMessage = (data) => {
|
||||
const msg = data.result;
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
if (typeof msg.type === 'string' && msg.type === 'error') {
|
||||
this.addError('Error Connecting to Server');
|
||||
}
|
||||
// todo: handle message
|
||||
runInAction(() => {
|
||||
this.addMessage({ ...msg.message, sender: 'other', id: Date.now().toString(16), });
|
||||
console.log(toJS(this.messages), 'messages');
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage = (msg) => {
|
||||
const msgObj = {
|
||||
type: 'message',
|
||||
message: {
|
||||
sender: 'me',
|
||||
content: msg,
|
||||
readState: false,
|
||||
id: Date.now().toString(16),
|
||||
},
|
||||
};
|
||||
realtimeAPI.sendMessage(msgObj);
|
||||
this.addMessage(msgObj.message);
|
||||
}
|
||||
|
||||
errors = [];
|
||||
messages = [];
|
||||
}
|
||||
|
||||
export default Conversations;
|
@ -0,0 +1,41 @@
|
||||
// App.js
|
||||
|
||||
import React from 'react';
|
||||
import { Layout, Menu } from 'antd';
|
||||
// import './App.css';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
function App() {
|
||||
const channels = ['General', 'Random'];
|
||||
const messages = [
|
||||
{ user: 'User1', text: 'Hello!' },
|
||||
{ user: 'User2', text: 'Hi!' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider width={200}>
|
||||
<Menu mode="inline" style={{ height: '100%', borderRight: 0 }}>
|
||||
{channels.map(channel => (
|
||||
<Menu.Item key={channel}>{channel}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header style={{ background: '#fff', padding: 0 }} />
|
||||
<Content style={{ margin: '24px 16px 0', overflow: 'initial' }}>
|
||||
<div style={{ padding: 24, background: '#fff', textAlign: 'center' }}>
|
||||
{messages.map((message, index) => (
|
||||
<p key={index}>
|
||||
<strong>{message.user}</strong>: {message.text}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
@ -0,0 +1,44 @@
|
||||
import { Layout, Menu, List, Timeline, Input } from 'antd';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
function ChatApp() {
|
||||
return (
|
||||
<Layout>
|
||||
<Sider theme="light" width={300}>
|
||||
<Menu>
|
||||
<Menu.Item>Unread</Menu.Item>
|
||||
<Menu.Item>Mentions</Menu.Item>
|
||||
<Menu.Item>Favorites</Menu.Item>
|
||||
<Menu.Item>Channel List</Menu.Item>
|
||||
<Menu.Item>Direct Messages</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
<List>
|
||||
{/* Show channels and DMs */}
|
||||
</List>
|
||||
</Sider>
|
||||
|
||||
<Content>
|
||||
<Layout>
|
||||
<Sider theme="light">
|
||||
<List>
|
||||
{/* Show user profile cards */}
|
||||
</List>
|
||||
</Sider>
|
||||
|
||||
<Content>
|
||||
<Timeline>
|
||||
{/* Show messages */}
|
||||
</Timeline>
|
||||
|
||||
<Input.Search
|
||||
enterButton="Send"
|
||||
/>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatApp;
|
@ -0,0 +1,59 @@
|
||||
import { useEffect, useContext } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Layout } from 'antd';
|
||||
import Messages from './Components/Messages';
|
||||
import InputBox from './Components/InputBox';
|
||||
import Conversations from './Components/Conversations';
|
||||
import CustomerProfile from './Components/CustomerProfile';
|
||||
|
||||
import WebSocketLib from '@/lib/websocketLib';
|
||||
import { useStore } from '@/stores/StoreContext.js';
|
||||
|
||||
const customer = { url: 'ws://202.103.68.144:8888/whatever/', authToken: 'customer1Token' };
|
||||
// Create a WebSocketLib instance for each customer
|
||||
// const wsConnect = new WebSocketLib(customer.url, customer.authToken, 'WhatApp');
|
||||
// const wsConnect = new WebSocketLib(customer.url, customer.authToken, 'aaa');
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
const CList = [
|
||||
{ name: 'Customer_1', label: 'Customer_1', key: 'Customer_1', value: 'Customer_1' },
|
||||
{ name: 'Customer_2', label: 'Customer_2', key: 'Customer_2', value: 'Customer_2' },
|
||||
{ name: 'Customer_3', label: 'Customer_3', key: 'Customer_3', value: 'Customer_3' },
|
||||
{ name: 'Customer_4', label: 'Customer_4', key: 'Customer_4', value: 'Customer_4' },
|
||||
];
|
||||
const messages = [
|
||||
{ sender: 'Customer_1', text: 'Hello, how can I help you today?' },
|
||||
{ sender: 'Customer_2', text: 'Hello, how can I help you today?' },
|
||||
{ sender: 'Customer_3', text: 'Hello, how can I help you today?' },
|
||||
{ sender: 'Customer_4', text: 'Hello, how can I help you today?' },
|
||||
];
|
||||
const ChatWindow = observer(() => {
|
||||
const { conversationsStore } = useStore();
|
||||
const { sendMessage, messages } = conversationsStore;
|
||||
useEffect(() => {
|
||||
|
||||
return () => {};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout style={{maxHeight: 'calc(100% - 150px)', height: 'calc(100% - 150px)'}}>
|
||||
<Sider width={300} theme={'light'} style={{ height: '70vh' }}>
|
||||
<Conversations conversations={CList} />
|
||||
</Sider>
|
||||
|
||||
<Content className='h70' style={{maxHeight: '70vh', height: '70vh'}}>
|
||||
<Layout style={{ height: '100%' }}>
|
||||
<Messages />
|
||||
<InputBox onSend={(v) => sendMessage(v)} />
|
||||
</Layout>
|
||||
</Content>
|
||||
|
||||
<Sider width={300} theme={'light'}>
|
||||
<CustomerProfile customer={{}} />
|
||||
</Sider>
|
||||
</Layout>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChatWindow;
|
@ -1,38 +0,0 @@
|
||||
|
||||
import { Layout } from 'antd';
|
||||
import Messages from './Messages';
|
||||
import InputBox from './InputBox';
|
||||
import Conversations from './Conversations';
|
||||
import CustomerProfile from './CustomerProfile';
|
||||
|
||||
const {Sider, Content } = Layout;
|
||||
|
||||
const CList = [
|
||||
{ name: 'Customer_1', label: 'Customer_1', key: 'Customer_1', value: 'Customer_1' },
|
||||
{ name: 'Customer_2', label: 'Customer_2', key: 'Customer_2', value: 'Customer_2' },
|
||||
{ name: 'Customer_3', label: 'Customer_3', key: 'Customer_3', value: 'Customer_3' },
|
||||
{ name: 'Customer_4', label: 'Customer_4', key: 'Customer_4', value: 'Customer_4' },
|
||||
];
|
||||
function ChatWindow() {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Sider width={300} theme={'light'}>
|
||||
<Conversations conversations={CList} />
|
||||
</Sider>
|
||||
|
||||
<Content>
|
||||
<Layout>
|
||||
<Messages />
|
||||
<InputBox />
|
||||
</Layout>
|
||||
</Content>
|
||||
|
||||
<Sider width={300} theme={'light'}>
|
||||
<CustomerProfile customer={{}}/>
|
||||
</Sider>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatWindow;
|
@ -1,31 +1,55 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import { List, Avatar } from 'antd';
|
||||
import PropTypes from 'prop-types';
|
||||
import crypto from 'crypto-js';
|
||||
|
||||
const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
|
||||
function Conversations({ conversations }) {
|
||||
const ColorList = []; // ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
|
||||
// const colors = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const red = Math.floor(Math.random() * 256).toString(16);
|
||||
const green = Math.floor(Math.random() * 256).toString(16);
|
||||
const blue = Math.floor(Math.random() * 256).toString(16);
|
||||
// ColorList.push(`rgb(${red}, ${green}, ${blue})`);
|
||||
ColorList.push(`#${red}${green}${blue}`);
|
||||
}
|
||||
console.log(ColorList);
|
||||
const stringToColour = (str) => {
|
||||
// Hash the username using SHA256
|
||||
const hash = crypto.SHA256(str);
|
||||
|
||||
// Convert the hash to a hexadecimal string
|
||||
const hexString = hash.toString(crypto.enc.Hex);
|
||||
|
||||
// Use the first 6 characters of the hex string as a color
|
||||
const color = '#' + hexString.substring(0, 6);
|
||||
|
||||
return color;
|
||||
};
|
||||
|
||||
/**
|
||||
* []
|
||||
*/
|
||||
const Conversations = observer(({ conversations }) => {
|
||||
return (
|
||||
<List
|
||||
dataSource={conversations}
|
||||
renderItem={(item, ii) => (
|
||||
<List.Item>
|
||||
<List.Item actions={[<a key='list-loadmore-edit'>mark</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: ColorList[ii],
|
||||
backgroundColor: stringToColour(item.name), // ColorList[ii],
|
||||
verticalAlign: 'middle',
|
||||
}}>
|
||||
{item.name}
|
||||
</Avatar>
|
||||
}
|
||||
title={item.name}
|
||||
description='{最近的消息}'
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Conversations.propTypes = {
|
||||
conversations: PropTypes.string.isRequired,
|
||||
};
|
||||
});
|
||||
export default Conversations;
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import { Card } from 'antd';
|
||||
|
||||
function CustomerProfile({ customer }) {
|
||||
const CustomerProfile = observer(({ customer }) => {
|
||||
return <Card title={customer.name}>{/* other profile details */}</Card>;
|
||||
}
|
||||
CustomerProfile.propTypes = {
|
||||
};
|
||||
});
|
||||
export default CustomerProfile;
|
||||
|
@ -1,22 +1,34 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Input, Button } from 'antd';
|
||||
|
||||
function InputBox({ onSend }) {
|
||||
|
||||
function handleSend() {
|
||||
// Logic to get message and call onSend
|
||||
const InputBox = observer(({ onSend }) => {
|
||||
const [message, setMessage] = useState('');
|
||||
const handleSend = (v) => {
|
||||
// console.log(v);
|
||||
if (typeof onSend === 'function' && v.trim() !== '') {
|
||||
onSend(v);
|
||||
setMessage('');
|
||||
}
|
||||
}
|
||||
const sendMessage = async () => {
|
||||
if (message.trim() !== '') {
|
||||
// const api = new RealTimeAPI('wss://your_rocket_chat_server_url/websocket');
|
||||
// await api.login('your_username', 'your_password'); // replace with your actual username and password
|
||||
// await api.sendMessage({ roomId: 'ROOM_ID', msg: message }); // replace 'ROOM_ID' with your actual room id
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder="Type message here"
|
||||
enterButton="Send"
|
||||
size="large"
|
||||
onSearch={handleSend}
|
||||
/>
|
||||
{/* <Input.TextArea rows={4} />
|
||||
<Button type="primary" className="chat-button">
|
||||
Send
|
||||
</Button> */}
|
||||
<Input.Search placeholder='Type message here' enterButton='Send' size='large' onSearch={handleSend} defaultValue={message} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default InputBox;
|
||||
|
@ -1,21 +1,36 @@
|
||||
import { useEffect } from 'react';
|
||||
import { List, Avatar, Timeline } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { useStore } from '@/stores/StoreContext.js';
|
||||
|
||||
import { List, Avatar } from 'antd';
|
||||
const Messages = observer(() => {
|
||||
const { conversationsStore } = useStore();
|
||||
const { messages: newMessages } = conversationsStore;
|
||||
|
||||
useEffect(() => {
|
||||
// Load your data here
|
||||
// For example:
|
||||
// conversationsStore.loadMessages();
|
||||
}, [newMessages]);
|
||||
|
||||
function Messages({ messages }) {
|
||||
return (
|
||||
<List
|
||||
dataSource={messages}
|
||||
renderItem={message => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar>{message.sender[0]}</Avatar>}
|
||||
title={message.sender}
|
||||
/>
|
||||
<div>{message.text}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<>
|
||||
<List
|
||||
dataSource={conversationsStore.messages}
|
||||
style={{ flex: '1 1' }}
|
||||
renderItem={(message) => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
// avatar={<Avatar>{message.sender[0]}</Avatar>}
|
||||
title={message.sender !== 'me' ? message.sender : ''}
|
||||
description={message.sender !== 'me' ? `(${message.id}) ${message.content}` : ''}
|
||||
/>
|
||||
{message.sender === 'me' && <div>{message.content} ({message.id})</div>}
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Messages;
|
||||
|
Loading…
Reference in New Issue