Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a6cd4d242 |
@ -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"
|
||||
|
32
src/index.js
32
src/index.js
@ -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)
|
||||
|
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