Compare commits
No commits in common. "spyX" and "main" have entirely different histories.
@ -15,8 +15,7 @@
|
|||||||
"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"
|
||||||
|
30
src/index.js
30
src/index.js
@ -4,8 +4,7 @@ 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 { v4 } from 'uuid';
|
import './config/mongo';
|
||||||
// import './config/mongo';
|
|
||||||
|
|
||||||
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
|
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
|
||||||
// Routers
|
// Routers
|
||||||
@ -13,10 +12,7 @@ import authRouter from './modules/auth';
|
|||||||
import userRouter from './modules/user';
|
import userRouter from './modules/user';
|
||||||
|
|
||||||
// Ws
|
// Ws
|
||||||
import ClientHandle from "./ws/clientHandle";
|
import { getDevices } from './ws/device';
|
||||||
import ClientConnectionManager from "./ws/clientConnectionManager";
|
|
||||||
|
|
||||||
const clientConnectionManager = new ClientConnectionManager();
|
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(swagger({
|
.use(swagger({
|
||||||
@ -51,25 +47,17 @@ 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.Optional(t.String()),
|
type: t.String(),
|
||||||
content: t.Optional(t.Any()),
|
d: t.String(),
|
||||||
}),
|
}),
|
||||||
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) {
|
message(ws, message) {
|
||||||
ws.data.store.clientHandle.handleClientMessage(message);
|
switch(message.type) {
|
||||||
|
case 'get-device':
|
||||||
|
getDevices(ws, message.d);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
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)
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
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;
|
|
7
src/ws/device.js
Normal file
7
src/ws/device.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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