准备完成通讯模块
This commit is contained in:
@ -29,7 +29,10 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
// throw FatalError("Server mode has not been implemented yet", 1);
|
// throw FatalError("Server mode has not been implemented yet", 1);
|
||||||
std::unordered_map<std::string, SessionClass> session_map;
|
std::unordered_map<std::string, SessionClass> session_map;
|
||||||
std::string cmd;
|
std::string cmd;
|
||||||
|
std::ofstream fout("/tmp/log.txt");
|
||||||
while (std::getline(std::cin, cmd)) {
|
while (std::getline(std::cin, cmd)) {
|
||||||
|
fout << cmd << std::endl;
|
||||||
|
fout.flush();
|
||||||
if (cmd[1] == 'O') //`#OpenSession [TempChannelID]`
|
if (cmd[1] == 'O') //`#OpenSession [TempChannelID]`
|
||||||
{
|
{
|
||||||
std::string new_session_token = GenerateRandomString(10);
|
std::string new_session_token = GenerateRandomString(10);
|
||||||
@ -42,6 +45,7 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
ss >> temp_channel_id;
|
ss >> temp_channel_id;
|
||||||
std::cout << temp_channel_id << " Init 1\n"
|
std::cout << temp_channel_id << " Init 1\n"
|
||||||
<< new_session_token << ' ' << new_outh_token << std::endl;
|
<< new_session_token << ' ' << new_outh_token << std::endl;
|
||||||
|
std::cout.flush();
|
||||||
} else if (cmd[1] == 'S')
|
} else if (cmd[1] == 'S')
|
||||||
return;
|
return;
|
||||||
else if (cmd[1] == 'C') {
|
else if (cmd[1] == 'C') {
|
||||||
@ -52,16 +56,19 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
if (session_map.find(session_token) == session_map.end()) {
|
if (session_map.find(session_token) == session_map.end()) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
session_map.erase(session_token);
|
session_map.erase(session_token);
|
||||||
std::cout << session_token << ' ' << operation_token << " 0"
|
std::cout << session_token << ' ' << operation_token << " 0"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
} else if (cmd[1] == 'W') {
|
} else if (cmd[1] == 'W') {
|
||||||
std::stringstream ss(cmd);
|
std::stringstream ss(cmd);
|
||||||
std::string session_token, operation_token, authentic_key;
|
std::string session_token, operation_token, authentic_key;
|
||||||
@ -70,21 +77,25 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
if (session_map.find(session_token) == session_map.end()) {
|
if (session_map.find(session_token) == session_map.end()) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (session_map[session_token].login_stack.size())
|
if (session_map[session_token].login_stack.size()) {
|
||||||
std::cout << session_token << ' ' << operation_token << " 1\n"
|
std::cout << session_token << ' ' << operation_token << " 1\n"
|
||||||
<< engine.QueryUserInfo(
|
<< engine.QueryUserInfo(
|
||||||
session_map[session_token].login_stack.top().first)
|
session_map[session_token].login_stack.top().first)
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
else {
|
std::cout.flush();
|
||||||
|
} else {
|
||||||
std::cout << session_token << ' ' << operation_token
|
std::cout << session_token << ' ' << operation_token
|
||||||
<< " 1\n[nobody] -1" << std::endl;
|
<< " 1\n[nobody] -1" << std::endl;
|
||||||
|
std::cout.flush();
|
||||||
}
|
}
|
||||||
} else if (cmd[1] == 'R') {
|
} else if (cmd[1] == 'R') {
|
||||||
std::stringstream ss(cmd);
|
std::stringstream ss(cmd);
|
||||||
@ -94,11 +105,13 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
if (session_map.find(session_token) == session_map.end()) {
|
if (session_map.find(session_token) == session_map.end()) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
if (session_map[session_token].OuthorizationKey != authentic_key) {
|
||||||
std::cout << session_token << ' ' << operation_token << " -1"
|
std::cout << session_token << ' ' << operation_token << " -1"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
std::cout.flush();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::getline(std::cin, cmd);
|
std::getline(std::cin, cmd);
|
||||||
@ -107,6 +120,7 @@ void BookStoreMain(bool is_server, std::string config_dir) {
|
|||||||
std::cout << session_token << ' ' << operation_token << " "
|
std::cout << session_token << ' ' << operation_token << " "
|
||||||
<< ret.size() << std::endl;
|
<< ret.size() << std::endl;
|
||||||
for (auto &line : ret) std::cout << line << std::endl;
|
for (auto &line : ret) std::cout << line << std::endl;
|
||||||
|
std::cout.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,50 +4,56 @@
|
|||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<title>Socket.IO chat</title>
|
<title>Socket.IO chat</title>
|
||||||
<style>
|
<style>
|
||||||
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
||||||
|
|
||||||
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
|
|
||||||
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
|
|
||||||
#input:focus { outline: none; }
|
|
||||||
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
|
|
||||||
|
|
||||||
#messages { list-style-type: none; margin: 0; padding: 0; }
|
|
||||||
#messages > li { padding: 0.5rem 1rem; }
|
|
||||||
#messages > li:nth-child(odd) { background: #efefef; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ul id="messages"></ul>
|
<!-- <ul id="messages"></ul>
|
||||||
<form id="form" action="">
|
<form id="form" action="">
|
||||||
<input id="input" autocomplete="off" /><button>Send</button>
|
<textarea id="input" autocomplete="off" ></textarea><button id="submit_button" type="button">Send</button>
|
||||||
</form>
|
</form> -->
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const socket = io();
|
const socket = io();
|
||||||
const form = document.getElementById('form');
|
// const form = document.getElementById('form');
|
||||||
const input = document.getElementById('input');
|
// const input = document.getElementById('input');
|
||||||
const messages = document.getElementById('messages');
|
// const messages = document.getElementById('messages');
|
||||||
form.addEventListener("keydown", function(event){
|
// form.addEventListener("keydown", function(event) {
|
||||||
if (event.key === "Enter" && event.ctrlKey) {
|
// if (event.key === "Enter") {
|
||||||
event.preventDefault();
|
// event.stopPropagation();
|
||||||
if (input.value) {
|
// console.log('enter triggered');
|
||||||
socket.emit('chat message', input.value);
|
// if (event.ctrlKey) {
|
||||||
input.value = '';
|
// console.log('ctrl triggered');
|
||||||
}
|
// event.preventDefault();
|
||||||
}
|
// if (input.value) {
|
||||||
});
|
// socket.emit('request', input.value);
|
||||||
form.addEventListener('submit', (e) => {
|
// input.value = '';
|
||||||
e.preventDefault();
|
// }
|
||||||
if (input.value) {
|
// } else {
|
||||||
socket.emit('chat message', input.value);
|
// console.log('ctrl not triggered');
|
||||||
input.value = '';
|
// event.preventDefault();
|
||||||
}
|
// input.value=input.value;
|
||||||
});
|
// }
|
||||||
socket.on('chat message', (msg) => {
|
// }
|
||||||
const item = document.createElement('li');
|
// });
|
||||||
item.textContent = msg;
|
// form.addEventListener('submit', (e) => {
|
||||||
messages.appendChild(item);
|
// console.log('submit triggered');
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
// e.preventDefault();
|
||||||
|
// });
|
||||||
|
// var button = document.getElementById("submit_button");
|
||||||
|
// button.addEventListener('click', function(e) {
|
||||||
|
// e.preventDefault();
|
||||||
|
// console.log('button triggered');
|
||||||
|
// if (input.value) {
|
||||||
|
// socket.emit('request', input.value);
|
||||||
|
// input.value = '';
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
socket.on('response', (msg) => {
|
||||||
|
console.log("Get response from sever: "+msg);
|
||||||
|
// const item = document.createElement('div');
|
||||||
|
// item.textContent = msg;
|
||||||
|
// messages.appendChild(item);
|
||||||
|
// window.scrollTo(0, document.body.scrollHeight);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -6,20 +6,150 @@ const { Server } = require('socket.io');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
|
class Queue {
|
||||||
|
constructor() {
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加元素到队列的尾部
|
||||||
|
enqueue(element) {
|
||||||
|
this.items.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从队列的头部移除元素并返回移除的元素
|
||||||
|
dequeue() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.items.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回队列的头部元素
|
||||||
|
front() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查队列是否为空
|
||||||
|
isEmpty() {
|
||||||
|
return this.items.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回队列的大小
|
||||||
|
size() {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空队列
|
||||||
|
clear() {
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
const message_map=new Map();
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const backend=spawn('/workspaces/BH-Bookstore-2023/build/code',['-s','-c','/tmp/conf/']);
|
||||||
|
|
||||||
|
const AsyncLock = require('async-lock');
|
||||||
|
const lock = new AsyncLock();
|
||||||
|
|
||||||
|
function SendRequest(data) {
|
||||||
|
console.log("SendRequest called");
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
lock.acquire('myLock', (done) => {
|
||||||
|
console.log("SendRequest acquired lock");
|
||||||
|
console.log("sending data to backend: "+data)
|
||||||
|
backend.stdin.write(data+'\n', (error) => {
|
||||||
|
done();
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetResult(session_token,operation_token) {
|
||||||
|
console.log("GetResult called");
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
if(message_map.has(session_token))
|
||||||
|
{
|
||||||
|
if(message_map.get(session_token).has(operation_token))
|
||||||
|
{
|
||||||
|
const ret=message_map.get(session_token).get(operation_token);
|
||||||
|
message_map.get(session_token).delete(operation_token);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let buffer = '';
|
||||||
|
const response_queue=new Queue();
|
||||||
|
backend.stdout.on('data', (data) => {
|
||||||
|
console.log(`stdout: ${data}`);
|
||||||
|
buffer += data.toString();
|
||||||
|
while (buffer.includes('\n')) {
|
||||||
|
const lineEnd = buffer.indexOf('\n');
|
||||||
|
const line = buffer.slice(0, lineEnd);
|
||||||
|
buffer = buffer.slice(lineEnd + 1);
|
||||||
|
console.log(`C++ Program Output: ${line}`);
|
||||||
|
response_queue.enqueue(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// SendRequest("#OpenSession InnerTest")
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(join(__dirname, 'index.html'));
|
res.sendFile(join(__dirname, 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
console.log('a user connected');
|
console.log('a user connected');
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('user disconnected');
|
console.log('user disconnected');
|
||||||
});
|
});
|
||||||
socket.on('chat message', (msg) => {
|
socket.on('request', async (msg) => {
|
||||||
console.log('message: ' + msg);
|
console.log('message: ' + msg);
|
||||||
socket.emit('chat message', '[self]'+msg);
|
const substrings = msg.trim().split('\n')[0].split(' ');
|
||||||
socket.broadcast.emit('chat message', '[other]'+msg);
|
const head=substrings[0];
|
||||||
|
if(head[1]=='S')
|
||||||
|
{
|
||||||
|
backend.stdin.write("#ShutDownSystem\n");
|
||||||
|
sleep(1000);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
const session_token=substrings[1];
|
||||||
|
if(head[1]=='O')
|
||||||
|
{
|
||||||
|
SendRequest(msg);
|
||||||
|
ret=await GetResult(session_token,"Init");
|
||||||
|
console.log("ret: "+ret);
|
||||||
|
socket.emit('response', ret);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const operation_token=substrings[2];
|
||||||
|
const outhentication_key=substrings[3];
|
||||||
|
const command=msg.trim().split('\n')[1];
|
||||||
|
console.log('head: ' + head);
|
||||||
|
console.log('session_token: ' + session_token);
|
||||||
|
console.log('operation_token: ' + operation_token);
|
||||||
|
console.log('outhentication_key: ' + outhentication_key);
|
||||||
|
console.log('command: ' + command);
|
||||||
|
console.log("sending request to backend")
|
||||||
|
SendRequest(msg);
|
||||||
|
if(!message_map.has(session_token)) message_map.set(session_token, new Map());
|
||||||
|
ret=await GetResult(session_token,operation_token);
|
||||||
|
console.log("ret: "+ret);
|
||||||
|
socket.emit('response', ret);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
26
frontend/Web/package-lock.json
generated
26
frontend/Web/package-lock.json
generated
@ -9,7 +9,10 @@
|
|||||||
"version": "0.0.0.1",
|
"version": "0.0.0.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async-lock": "^1.4.0",
|
||||||
|
"async-mutex": "^0.4.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"mutex-js": "^1.1.5",
|
||||||
"socket.io": "^4.7.2"
|
"socket.io": "^4.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -56,6 +59,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/async-lock": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ=="
|
||||||
|
},
|
||||||
|
"node_modules/async-mutex": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base64id": {
|
"node_modules/base64id": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
@ -519,6 +535,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/mutex-js": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/mutex-js/-/mutex-js-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-e0PCNr2q4Er5SNrRwE/1yDFe8ILOk0yS4xFuDbO0KOfr3/5VmUcWB6SaTFaxRPqC/MWiGkfBuTRgIGz5EJtlcA=="
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
@ -808,6 +829,11 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
|
},
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "1.6.18",
|
"version": "1.6.18",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
@ -11,7 +11,10 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async-lock": "^1.4.0",
|
||||||
|
"async-mutex": "^0.4.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"mutex-js": "^1.1.5",
|
||||||
"socket.io": "^4.7.2"
|
"socket.io": "^4.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user