Feat: init server
This commit is contained in:
parent
ac10b685af
commit
2a6cd4d242
@ -15,7 +15,8 @@
|
|||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"elysia-helmet": "^1.0.2",
|
"elysia-helmet": "^1.0.2",
|
||||||
"mongoose": "^8.1.0"
|
"mongoose": "^8.1.0",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "latest"
|
"bun-types": "latest"
|
||||||
|
32
src/index.js
32
src/index.js
@ -4,7 +4,8 @@ import { cors } from '@elysiajs/cors';
|
|||||||
import { helmet } from 'elysia-helmet';
|
import { helmet } from 'elysia-helmet';
|
||||||
import { jwt } from '@elysiajs/jwt';
|
import { jwt } from '@elysiajs/jwt';
|
||||||
import { logger } from '@grotto/logysia';
|
import { logger } from '@grotto/logysia';
|
||||||
import './config/mongo';
|
import { v4 } from 'uuid';
|
||||||
|
// import './config/mongo';
|
||||||
|
|
||||||
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
|
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
|
||||||
// Routers
|
// Routers
|
||||||
@ -12,7 +13,10 @@ import authRouter from './modules/auth';
|
|||||||
import userRouter from './modules/user';
|
import userRouter from './modules/user';
|
||||||
|
|
||||||
// Ws
|
// Ws
|
||||||
import { getDevices } from './ws/device';
|
import ClientHandle from "./ws/clientHandle";
|
||||||
|
import ClientConnectionManager from "./ws/clientConnectionManager";
|
||||||
|
|
||||||
|
const clientConnectionManager = new ClientConnectionManager();
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(swagger({
|
.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 dùng hệ thống auth riêng nên không phải đặt trước onBeforeHandle auth của http
|
||||||
.ws('/ws', {
|
.ws('/ws', {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
type: t.String(),
|
type: t.Optional(t.String()),
|
||||||
d: t.String(),
|
content: t.Optional(t.Any()),
|
||||||
}),
|
}),
|
||||||
message(ws, message) {
|
open: (ws) => {
|
||||||
switch(message.type) {
|
const idSocket = v4();
|
||||||
case 'get-device':
|
const clientHandle = new ClientHandle(idSocket, clientConnectionManager);
|
||||||
getDevices(ws, message.d);
|
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
|
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
|
// không auth
|
||||||
.use(authRouter)
|
.use(authRouter)
|
||||||
|
41
src/ws/clientConnectionManager.js
Normal file
41
src/ws/clientConnectionManager.js
Normal 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
190
src/ws/clientHandle.js
Normal 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;
|
@ -1,7 +0,0 @@
|
|||||||
async function getDevices(ws, d) {
|
|
||||||
const { data: { store } } = ws;
|
|
||||||
console.log(store);
|
|
||||||
ws.send({ d });
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getDevices };
|
|
Loading…
x
Reference in New Issue
Block a user