Feat: init server

This commit is contained in:
koh 2024-01-26 20:07:25 +07:00
parent ac10b685af
commit 2a6cd4d242
6 changed files with 255 additions and 18 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -15,7 +15,8 @@
"bcrypt": "^5.1.1",
"elysia": "latest",
"elysia-helmet": "^1.0.2",
"mongoose": "^8.1.0"
"mongoose": "^8.1.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"bun-types": "latest"

View File

@ -4,7 +4,8 @@ import { cors } from '@elysiajs/cors';
import { helmet } from 'elysia-helmet';
import { jwt } from '@elysiajs/jwt';
import { logger } from '@grotto/logysia';
import './config/mongo';
import { v4 } from 'uuid';
// import './config/mongo';
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
// Routers
@ -12,7 +13,10 @@ import authRouter from './modules/auth';
import userRouter from './modules/user';
// Ws
import { getDevices } from './ws/device';
import ClientHandle from "./ws/clientHandle";
import ClientConnectionManager from "./ws/clientConnectionManager";
const clientConnectionManager = new ClientConnectionManager();
const app = new Elysia()
.use(swagger({
@ -47,17 +51,25 @@ const app = new Elysia()
// ws dùng hệ thống auth riêng nên không phải đặt trước onBeforeHandle auth của http
.ws('/ws', {
body: t.Object({
type: t.String(),
d: t.String(),
type: t.Optional(t.String()),
content: t.Optional(t.Any()),
}),
message(ws, message) {
switch(message.type) {
case 'get-device':
getDevices(ws, message.d);
}
open: (ws) => {
const idSocket = v4();
const clientHandle = new ClientHandle(idSocket, clientConnectionManager);
clientHandle.initialize(ws);
clientConnectionManager.addClient(idSocket, clientHandle);
ws.data.store.clientHandle = clientHandle;
},
close: (ws) => {
ws.data.store.clientHandle.handleClientDisconnection();
},
message(ws, message) {
ws.data.store.clientHandle.handleClientMessage(message);
},
response: t.String(),
maxPayloadLength: 1024 * 1024, // 1 MB
beforeHandle: ({ headers, jwt, set, store }) => isAuthenticatedWS({ headers, jwt, set, store }),
// beforeHandle: ({ headers, jwt, set, store }) => isAuthenticatedWS({ headers, jwt, set, store }),
})
// không auth
.use(authRouter)

View File

@ -0,0 +1,41 @@
export default class ClientConnectionManager {
instance;
clients = new Map()
getClient(id) {
return this.clients.get(id)
}
addClient(id, client) {
if (client.connectionState !== 'OPEN') {
console.warn(`WebSocket is not open for client ${id}. Retrying...`)
setTimeout(() => this.addClient(id, client), 100) // Retry after 100ms
return
}
this.clients.set(id, client)
}
removeClient(id) {
const client = this.clients.get(id)
if (!client) {
return
}
// Close the WebSocket, regardless of its state
client.close()
// Remove from all maps
this.clients.delete(id)
}
isClientActive(id) {
const client = this.clients.get(id)
return client !== undefined && client.connectionState === 'OPEN'
}
getAllClients() {
return Array.from(this.clients.values()).filter(
(clientHandler) => clientHandler.ws.raw.readyState === WebSocket.OPEN,
)
}
}

190
src/ws/clientHandle.js Normal file
View File

@ -0,0 +1,190 @@
class ClientHandle {
id;
ws;
clientConnectionManager;
connectionState = "CLOSE";
constructor(id, clientConnectionManager) {
this.id = id;
this.clientConnectionManager = clientConnectionManager;
}
initialize(ws) {
if (!ws) {
return;
}
this.ws = ws;
this.connectionState = "OPEN"; // Set the state to OPEN here
}
isValidJSON(json) {
json = JSON.stringify(json);
return /^[\],:{}\s]*$/.test(
json
.replace(/\\["\\\/bfnrtu]/g, "@")
.replace(
/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
"]"
)
.replace(/(?:^|:|,)(?:\s*\[)+/g, "")
);
}
handleClientMessage(message) {
if (!this.isValidJSON(message)) {
return this.sendToClient({ ok: 0, e: "Không đúng định dạng JSON" });
}
const { type, content } = message;
switch (type) {
case "info": {
this.sendToClient({
type: "info",
content: { ok: 1, d: this.id },
});
break;
}
case "devices-lists": {
const allClient = this.clientConnectionManager.getAllClients();
const idList = [];
allClient.forEach((client) => {
if (client.id !== this.id) {
idList.push(client.id);
}
});
this.sendToClient({
type: "devices-lists",
content: { ok: 1, d: idList },
});
break;
}
case "send-to-web-offer": {
const { toDevice, offer } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "send-to-web-offer",
content: {
offer,
fromDevice: this.id,
},
});
break;
}
case "send-to-web-answer": {
const { toDevice, answer } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "send-to-web-answer",
content: {
answer,
fromDevice: this.id,
},
});
break;
}
case "ice": {
const { toDevice, ice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "ice",
content: {
ice,
},
});
break;
}
case "start": {
const { toDevice, fromDevice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "start",
content: {
fromDevice,
},
});
break;
}
case "stop": {
const { toDevice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "stop",
content: {},
});
break;
}
case "actionmouse": {
const { toDevice, type, x, y } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "actionmouse",
content: { type, x, y },
});
break;
}
case "action": {
const { toDevice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "action",
content: {
type: content.type,
},
});
}
case "start_stream_socket": {
const { toDevice, fromDevice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "start_stream_socket",
content: { fromDevice },
});
break;
}
case "frame_stream_socket": {
const { toDevice, image } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "frame_stream_socket",
content: { image },
});
break;
}
case "stop_stream_socket": {
const { toDevice } = content;
this.clientConnectionManager.getClient(toDevice)?.sendToClient({
type: "stop_stream_socket",
content: {},
});
break;
}
default:
break;
}
}
handleClientDisconnection() {
this.clientConnectionManager.removeClient(this.id.toString());
this.connectionState = "CLOSED";
}
sendToClient(data) {
try {
if (this.ws && this.connectionState === "OPEN") {
this.ws.send(JSON.stringify(data));
} else {
this.clientConnectionManager.removeClient(this.id.toString());
}
} catch (error) {
// Handle error
}
}
close() {
try {
if (this.ws && this.ws.readyState === this.ws.OPEN) {
this.sendToClient({
type: "a_device_disconnect",
content: {
device: this.id,
},
});
this.ws.close();
}
} catch (error) {}
}
}
export default ClientHandle;

View File

@ -1,7 +0,0 @@
async function getDevices(ws, d) {
const { data: { store } } = ws;
console.log(store);
ws.send({ d });
}
export { getDevices };