From 2a6cd4d2420daaacfbd57a1a6719396e9e659d0a Mon Sep 17 00:00:00 2001 From: koh Date: Fri, 26 Jan 2024 20:07:25 +0700 Subject: [PATCH] Feat: init server --- bun.lockb | Bin 37419 -> 37760 bytes package.json | 3 +- src/index.js | 32 +++-- src/ws/clientConnectionManager.js | 41 +++++++ src/ws/clientHandle.js | 190 ++++++++++++++++++++++++++++++ src/ws/device.js | 7 -- 6 files changed, 255 insertions(+), 18 deletions(-) create mode 100644 src/ws/clientConnectionManager.js create mode 100644 src/ws/clientHandle.js delete mode 100644 src/ws/device.js diff --git a/bun.lockb b/bun.lockb index 7014f5af12d10edc26d646a5a4195015aad6617d..c812f737525efba0b8dcd316726e4b37d3e59a0a 100755 GIT binary patch delta 5989 zcmeHLdvKK1760xgOY&{9fk+@(mavhKH`ye+Nj71V-2j1*d?6ALm!Le7Y#y7}=1o$Y zvLFm>B|<#nKrN*dMtMl|7uP4Ee;jy?R+1(|9wdV>RZUEiBt6)5NEz}X zo^-5A- zQ|Dk4WO#JIe6Odox4v1DS~|Q9JgIZg^Kjm-Mi`f*4ZU!CSdvZ$+n>VRc(MkJ!A?g( z@^GH6)}~%hf1jtTxx>@a+J6k~G44iRS9fQJw?&ekL@(%kE%gH}Y}toV*gzX3>j#5; z7|yUh6*3;u2+0ntLp{sokQ|?iAm0tU+-_b_U*Fy9ZK#)|t3`C`Z26}{A7vTeudck9GVA3Nhr`x~ z$!lRF7p-Kfjj)M_NRDud7pW4zpOQbqsYw>}+X#nnksRq1eyYUpDe~j@FEk$Ml#e3M zu(rN1x)foP(~ym&V0x7ef>nb>FcZI~aoH(8Bw6RwWQJ3f&LOsuU*{BW(l}H)E$N~h z@*3ppV%6T5&L)0A{wSyTfX1URkx8VBc8DgbjCRUWgd{CQvuGN_unWQFfk_k_Ws^t1 zmVgPWjkL+X0b3nlXlaU+qy=DF)!TX+*E_{-l4G3m>!`_BYc9z)IU4~iS6M8kK1$;; zPVMK=ndySgE*D@C@D}>QRW}E~xF^Qb*|bN%ifC!1UA#^56sOjJB`Bf`5q7y2PY$%x z5Nygj!MK-34bDZ9W1X@dxypV_QteiNaf>jDjkbyVX&fzn4_(l=2%9EePgQp~v=vC% ze9}kT<@@o(2Ey2N@dC+HopLhrvIIJbUDmDxD_7ed#8VOU8lJXzi{v<`JQZ2SQxxh1 zYQX{_K~MQ;qqCpHYdY||K~ zQ`HQIcI9;Vr&f>QiSry$#|*E5@e3spGFaB*RpuxoPN-W1#)c+QEb4~As=xvSQ%j4OErrkQ?z8XQ#tyEXpR1(RiX$yi4*d zr#5Z|e4`7Ac2PtASx#|;#_{`Cl1)y{EF)E!_#O9~>~+Xr{>Z3>CEg$Y$f)IZDWJj9 zNGuU*wcIYbFLp3V1DIG+N|K4HlOl^jq-6dj@J*o^}*$n$@e_4yKwi8C*nVTo;+uEJiBd8dVg~J(ZP(@uJl%A=lYhZfH$aK*gV56Cem`qo}wrA<7G)ob&#q~|E%y2ER76S_y;y( zRYV4jgAL`wzkEex(QrQeD}aAs*_2TL|G-8I6p=$$!L}E|zd}Xik-rfB+29|Tm5Ocf z4{V=J5d|~>wxt;hzKkfmM*f0sp{89Ezx0>_2>;6AA6Ok#mcu`=FDmu`U_UIhOZDPj#BSp@$U!@tFfSVwh> z;a>&(1FI*w0{($@Rw$y8&Q`dDn_`zVJ{%FQ=Rddn_V5;|r}~=vPjoN)NT5dU!48O{ zt#5>GR@t@f_@3)f--0T-UK1(KpE#gQ)oA8Tq0>tr7e{GL&4c1R9Z-yPT`4(Hc2`^+ zC8g%+_Ndlz;A*0^jSk*kzWgdsc+@pOXk)pZ^X_G31I!~8*T)6(AMCN{&_Pka97ReLjZ4jKLB{{ z{EgvVa|qxM(*VE+9vm*!<4_u8I*B5#asb3-BD90Y0Xz0#*a}0`~zNQI6O;8f}W-SdFJS zz-*uu@BkbW4goLBa)1v_cLHqyADor}n*cstaCrDYxezD=Oh6Kl&IcBhTsDZJD;cm_ z97*{}eV*?%d7J0rsUFt?w9c(vwI21nUOY3NCC`}c!~-~&t6>+A921TW$2=0?b>#Ks z<>B??<>I+;J0zn-o0{WkSF<){G6?>;@ht%-EdS}}D-AW+agaRFEIQk4+{lS%1Y>uT z01hCBoWswq^Lerem<#aoaoBh%c}aO`QvePpFGy-ohMu#6JR8ym6aoc69*_f=*(B$Q z1;__Dzqx{S6+k&K58ymmOd~Dv8}U~{&00GoKbHZV52b(u;MH9M@Gwzl^fkuE+W_@tU9~q>=)=YA@?u-y? z3(R@uJo-oLvV_q0By^*p(V!0sRkzI~x96fJQA34omV(e_nj&jUV_KGM(1^8GbFLY| zs6h{8yYKzFhfbfoaq_V^uE{GZGUv9_zBU8?b)RXoP8~xl16n=$eslK=?}TrQ!_CDP zZT6X=%eSA+nb>8u8Wq^h%Y$Kylo&>Lw_EV#+tzLsUGz-5!5F%U^PW2N+T+#ld>otv zR^}jmfL41cxx;D<-M~HJzuA;ppI(7h1+a*PJwrVm262%d>@XOwBdSPU-<;U6Q<;0a ziZudF%)HPObPn~Qi=zE0zPVXjn;PPTqcn-_LQxW9|(OfZJ7^Ac`+X;yX)yciq| zak)bCI}NzOxVO`aE1R92r!<*5THJL*6CO>u-fH}_9Pus{r|D&{Rh*-*yw;haySut` z?`wbjhnwjf3(N`G85Kh{-3b~)47GMw8v|EoxT?FHbNY{4V+YXA5?G7fRNE6r?{=HS zlN8pIm7F;xa9bF`bq&D|3V54mBF&INvcgy`(em-kel@6`)IZ^oQ zw78Meomf!7Bz)|ko)LnkD>72d%lR7;p_4B z_2GN&)hn-+JXemM$Q;hA)L1I%O)!45>0$Er8fJ#B2P==6mks`MBnbvMe_+5IM~8YX z#?XD@GnfC|aJcW}HnoB3)foM~*Puz8M$vs14brWwuNvt#+GoYa_To`2QsqwE2L@tb?@ zqxu1Z?)qTBPSOV2GO&PN8pxfo6K^}dl)f!R1-}zRF<&m*5NLAaVY*{*gWm1w>(6QM ibmj2NOwSJ*)l))Fw#u`M>Ee!LdTvO2;>h4;<39ncPGa@| delta 5712 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 diff --git a/package.json b/package.json index 09230de..9df50d3 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/index.js b/src/index.js index e3815c6..3541b10 100644 --- a/src/index.js +++ b/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) diff --git a/src/ws/clientConnectionManager.js b/src/ws/clientConnectionManager.js new file mode 100644 index 0000000..ee790cc --- /dev/null +++ b/src/ws/clientConnectionManager.js @@ -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, + ) + } +} \ No newline at end of file diff --git a/src/ws/clientHandle.js b/src/ws/clientHandle.js new file mode 100644 index 0000000..b2448d6 --- /dev/null +++ b/src/ws/clientHandle.js @@ -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; diff --git a/src/ws/device.js b/src/ws/device.js deleted file mode 100644 index 3ef0049..0000000 --- a/src/ws/device.js +++ /dev/null @@ -1,7 +0,0 @@ -async function getDevices(ws, d) { - const { data: { store } } = ws; - console.log(store); - ws.send({ d }); -} - -export { getDevices }; \ No newline at end of file