From 1f248e49960b3bda37a7cb8b6a111ac228f189e1 Mon Sep 17 00:00:00 2001 From: RblSb Date: Thu, 15 Aug 2024 08:29:24 +0300 Subject: [PATCH] Add uuids for better reconection Kick zombie users with same uuids. Minimal node version is 14. --- .github/workflows/main.yml | 2 +- README.md | 4 +- build/server.js | 98 +++++++++++++++++++++++------------- package-lock.json | 3 ++ package.json | 3 ++ res/client.js | 8 +-- src/Client.hx | 1 + src/Types.hx | 1 + src/client/ClientSettings.hx | 1 + src/client/Main.hx | 8 ++- src/server/Main.hx | 31 ++++++++++-- 11 files changed, 114 insertions(+), 46 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c93bc3..35c0c18 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: run: | node -v npm install --global lix - lix install haxe 4.3.0 --global + lix install haxe 4.3.6 --global lix download npm ci haxelib install tests.hxml --always diff --git a/README.md b/README.md index 6e7c30a..1266ed7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Default channel example: https://synctube.onrender.com/ ### Setup - Open `4200` port in your router settings (port is customizable) -- `npm install ws` in this project folder ([NodeJS](https://nodejs.org) required) +- `npm install ws` in this project folder ([NodeJS 14+](https://nodejs.org) required) - Run `node build/server.js` - Open showed "Local" link for yourself and send "Global" link to friends @@ -34,7 +34,7 @@ As alternative, you can install Docker and run: > docker run --rm -it -p 4200:4200 -v ${PWD}/user:/usr/src/app/user synctube > ``` -or +or > ```shell > docker compose up -d diff --git a/build/server.js b/build/server.js index 076051d..7279f04 100644 --- a/build/server.js +++ b/build/server.js @@ -523,7 +523,7 @@ JsonParser_$1.__name__ = true; JsonParser_$1.__super__ = json2object_reader_BaseParser; JsonParser_$1.prototype = $extend(json2object_reader_BaseParser.prototype,{ onIncorrectType: function(pos,variable) { - this.errors.push(json2object_Error.IncorrectType(variable,"{ ?updatePlaylist : Null<{ videoList : Array }>, ?updateClients : Null<{ clients : Array }>, type : WsEventType, ?togglePlaylistLock : Null<{ isOpen : Bool }>, ?toggleItemType : Null<{ pos : Int }>, ?skipVideo : Null<{ url : String }>, ?setTime : Null<{ time : Float }>, ?setRate : Null<{ rate : Float }>, ?setNextItem : Null<{ pos : Int }>, ?setLeader : Null<{ clientName : String }>, ?serverMessage : Null<{ textId : String }>, ?rewind : Null<{ time : Float }>, ?removeVideo : Null<{ url : String }>, ?playItem : Null<{ pos : Int }>, ?play : Null<{ time : Float }>, ?pause : Null<{ time : Float }>, ?message : Null<{ text : String, clientName : String }>, ?logout : Null<{ oldClientName : String, clients : Array, clientName : String }>, ?login : Null<{ ?passHash : Null, ?isUnknownClient : Null, ?clients : Null>, clientName : String }>, ?kickClient : Null<{ name : String }>, ?getYoutubeVideoInfo : Null<{ url : String, ?response : Null }>, ?getTime : Null<{ time : Float, ?rate : Null, ?paused : Null }>, ?dump : Null<{ data : String }>, ?connected : Null<{ videoList : Array, itemPos : Int, isUnknownClient : Bool, isPlaylistOpen : Bool, history : Array, globalIp : String, config : Config, clients : Array, clientName : String }>, ?banClient : Null<{ time : Float, name : String }>, ?addVideo : Null<{ item : VideoItem, atEnd : Bool }> }",pos)); + this.errors.push(json2object_Error.IncorrectType(variable,"{ ?updatePlaylist : Null<{ videoList : Array }>, ?updateClients : Null<{ clients : Array }>, type : WsEventType, ?togglePlaylistLock : Null<{ isOpen : Bool }>, ?toggleItemType : Null<{ pos : Int }>, ?skipVideo : Null<{ url : String }>, ?setTime : Null<{ time : Float }>, ?setRate : Null<{ rate : Float }>, ?setNextItem : Null<{ pos : Int }>, ?setLeader : Null<{ clientName : String }>, ?serverMessage : Null<{ textId : String }>, ?rewind : Null<{ time : Float }>, ?removeVideo : Null<{ url : String }>, ?playItem : Null<{ pos : Int }>, ?play : Null<{ time : Float }>, ?pause : Null<{ time : Float }>, ?message : Null<{ text : String, clientName : String }>, ?logout : Null<{ oldClientName : String, clients : Array, clientName : String }>, ?login : Null<{ ?passHash : Null, ?isUnknownClient : Null, ?clients : Null>, clientName : String }>, ?kickClient : Null<{ name : String }>, ?getYoutubeVideoInfo : Null<{ url : String, ?response : Null }>, ?getTime : Null<{ time : Float, ?rate : Null, ?paused : Null }>, ?dump : Null<{ data : String }>, ?connected : Null<{ videoList : Array, uuid : String, itemPos : Int, isUnknownClient : Bool, isPlaylistOpen : Bool, history : Array, globalIp : String, config : Config, clients : Array, clientName : String }>, ?banClient : Null<{ time : Float, name : String }>, ?addVideo : Null<{ item : VideoItem, atEnd : Bool }> }",pos)); json2object_reader_BaseParser.prototype.onIncorrectType.call(this,pos,variable); } ,loadJsonNull: function(pos,variable) { @@ -1862,7 +1862,7 @@ JsonParser_$7.__name__ = true; JsonParser_$7.__super__ = json2object_reader_BaseParser; JsonParser_$7.prototype = $extend(json2object_reader_BaseParser.prototype,{ onIncorrectType: function(pos,variable) { - this.errors.push(json2object_Error.IncorrectType(variable,"{ videoList : Array, itemPos : Int, isUnknownClient : Bool, isPlaylistOpen : Bool, history : Array, globalIp : String, config : Config, clients : Array, clientName : String }",pos)); + this.errors.push(json2object_Error.IncorrectType(variable,"{ videoList : Array, uuid : String, itemPos : Int, isUnknownClient : Bool, isPlaylistOpen : Bool, history : Array, globalIp : String, config : Config, clients : Array, clientName : String }",pos)); json2object_reader_BaseParser.prototype.onIncorrectType.call(this,pos,variable); } ,loadJsonNull: function(pos,variable) { @@ -1870,7 +1870,7 @@ JsonParser_$7.prototype = $extend(json2object_reader_BaseParser.prototype,{ } ,loadJsonObject: function(o,pos,variable) { var assigned = new haxe_ds_StringMap(); - this.objectSetupAssign(assigned,["clientName","clients","config","globalIp","history","isPlaylistOpen","isUnknownClient","itemPos","videoList"],[false,false,false,false,false,false,false,false,false]); + this.objectSetupAssign(assigned,["clientName","clients","config","globalIp","history","isPlaylistOpen","isUnknownClient","itemPos","uuid","videoList"],[false,false,false,false,false,false,false,false,false,false]); this.value = this.getAuto(); var _g = 0; while(_g < o.length) { @@ -1901,6 +1901,9 @@ JsonParser_$7.prototype = $extend(json2object_reader_BaseParser.prototype,{ case "itemPos": this.value.itemPos = this.loadObjectField(($_=new JsonParser_$52(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"itemPos",assigned,this.value.itemPos,pos); break; + case "uuid": + this.value.uuid = this.loadObjectField(($_=new JsonParser_$44(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"uuid",assigned,this.value.uuid,pos); + break; case "videoList": this.value.videoList = this.loadObjectField(($_=new JsonParser_$41(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"videoList",assigned,this.value.videoList,pos); break; @@ -1911,7 +1914,7 @@ JsonParser_$7.prototype = $extend(json2object_reader_BaseParser.prototype,{ this.objectErrors(assigned,pos); } ,getAuto: function() { - return { clientName : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), clients : new JsonParser_$49([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), config : new JsonParser_$81([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), globalIp : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), history : new JsonParser_$82([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isPlaylistOpen : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isUnknownClient : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), itemPos : new JsonParser_$52([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), videoList : new JsonParser_$41([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; + return { clientName : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), clients : new JsonParser_$49([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), config : new JsonParser_$81([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), globalIp : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), history : new JsonParser_$82([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isPlaylistOpen : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isUnknownClient : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), itemPos : new JsonParser_$52([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), uuid : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), videoList : new JsonParser_$41([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; } ,__class__: JsonParser_$7 }); @@ -3870,6 +3873,7 @@ js_Boot.__isNativeObj = function(o) { js_Boot.__resolveNativeClass = function(name) { return $global[name]; }; +var js_node_Crypto = require("crypto"); var js_node_Fs = require("fs"); var js_node_Http = require("http"); var js_node_Https = require("https"); @@ -4578,7 +4582,7 @@ var server_Main = function(opts) { preparePort = function() { server_Utils.isPortFree(_gthis.port,function(isFree) { if(!isFree && attempts > 0) { - haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 122, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 124, className : "server.Main", methodName : "new"}); attempts -= 1; _gthis.port++; preparePort(); @@ -4596,13 +4600,13 @@ server_Main.main = function() { server_Main.prototype = { runServer: function() { var _gthis = this; - haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 135, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 137, className : "server.Main", methodName : "runServer"}); if(this.config.localNetworkOnly) { - haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 137, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 139, className : "server.Main", methodName : "runServer"}); } else if(!this.isNoState) { server_Utils.getGlobalIp(function(ip) { _gthis.globalIp = ip; - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 141, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 143, className : "server.Main", methodName : "runServer"}); }); } var dir = "" + this.rootDir + "/res"; @@ -4687,7 +4691,7 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 210, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 212, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -4698,14 +4702,14 @@ server_Main.prototype = { var emote = _g1[_g]; ++_g; if(emoteCopies_h[emote.name]) { - haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 216, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 218, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.name] = true; if(!this.verbose) { continue; } if(emoteCopies_h[emote.image]) { - haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 220, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 222, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.image] = true; } @@ -4743,7 +4747,7 @@ server_Main.prototype = { js_node_Fs.writeFileSync("" + folder + "/users.json",JSON.stringify({ admins : users1, bans : _g, salt : users.salt},null,"\t")); } ,saveState: function() { - haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 259, className : "server.Main", methodName : "saveState"}); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 261, className : "server.Main", methodName : "saveState"}); var json = JSON.stringify(this.getCurrentState(),null,"\t"); js_node_Fs.writeFileSync(this.statePath,json); this.writeUsers(this.userList); @@ -4758,7 +4762,7 @@ server_Main.prototype = { if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 282, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 284, className : "server.Main", methodName : "loadState"}); var state = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); this.videoList.setItems(state.videoList); this.videoList.isOpen = state.isPlaylistOpen; @@ -4777,7 +4781,7 @@ server_Main.prototype = { this.videoTimer.pause(); } ,logError: function(type,data) { - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 300, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 302, className : "server.Main", methodName : "logError", customParams : [data]}); var crashesFolder = "" + this.rootDir + "/user/crashes"; server_Utils.ensureDir(crashesFolder); var name = DateTools.format(new Date(),"%Y-%m-%d_%H_%M_%S") + "-" + type; @@ -4795,7 +4799,7 @@ server_Main.prototype = { if(_gthis.clients.length == 0) { return; } - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 317, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 319, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,null,function(r) { }); }; @@ -4815,13 +4819,13 @@ server_Main.prototype = { password += this.config.salt; var hash = haxe_crypto_Sha256.encode(password); this.userList.admins.push({ name : name, hash : hash}); - haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 340, className : "server.Main", methodName : "addAdmin"}); + haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 342, className : "server.Main", methodName : "addAdmin"}); } ,removeAdmin: function(name) { HxOverrides.remove(this.userList.admins,Lambda.find(this.userList.admins,function(item) { return item.name == name; })); - haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 347, className : "server.Main", methodName : "removeAdmin"}); + haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 349, className : "server.Main", methodName : "removeAdmin"}); } ,replayLog: function(events) { var _gthis = this; @@ -4860,14 +4864,38 @@ server_Main.prototype = { } }; } + ,randomUuid: function() { + return js_node_Crypto.randomUUID(); + } + ,getUrlUuid: function(link) { + try { + if(StringTools.startsWith(link,"/")) { + link = "http://127.0.0.1" + link; + } + return new js_node_url_URL(link).searchParams.get("uuid"); + } catch( _g ) { + return null; + } + } ,onConnect: function(ws,req) { var _gthis = this; + var uuid; + var tmp = this.getUrlUuid(req.url); + uuid = tmp != null ? tmp : this.randomUuid(); + var oldClient = Lambda.find(this.clients,function(client) { + return client.uuid == uuid; + }); + if(oldClient != null) { + this.send(oldClient,{ type : "KickClient"}); + this.onMessage(oldClient,{ type : "Disconnected"},true); + } var ip = this.clientIp(req); var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; var name = "Guest " + (id + 1); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 385, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 408, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); var isAdmin = this.config.localAdmins && req.socket.localAddress == ip; var client = new Client(ws,req,id,name,0); + client.uuid = uuid; client.setGroupFlag(ClientGroup.Admin,isAdmin); this.clients.push(client); ws.on("pong",function() { @@ -4878,7 +4906,7 @@ server_Main.prototype = { var obj = _gthis.wsEventParser.fromJson(data.toString()); if(_gthis.wsEventParser.errors.length > 0 || _gthis.noTypeObj(obj)) { var errors = "" + ("Wrong request for type \"" + obj.type + "\":") + "\n" + json2object_ErrorUtils.convertErrorArray(_gthis.wsEventParser.errors); - haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 401, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 425, className : "server.Main", methodName : "onConnect"}); _gthis.serverMessage(client,errors); return; } @@ -4957,20 +4985,20 @@ server_Main.prototype = { return; } var name = data.banClient.name; - var bannedClient = ClientTools.getByName(this.clients,name); - if(bannedClient == null) { + var tmp = ClientTools.getByName(this.clients,name); + if(tmp == null) { return; } - if(client.name == name || (bannedClient.group & 8) != 0) { + if(client.name == name || (tmp.group & 8) != 0) { this.serverMessage(client,"adminsCannotBeBannedError"); return; } - var ip = this.clientIp(bannedClient.req); + var ip = this.clientIp(tmp.req); HxOverrides.remove(this.userList.bans,Lambda.find(this.userList.bans,function(item) { return item.ip == ip; })); if(data.banClient.time == 0) { - bannedClient.setGroupFlag(ClientGroup.Banned,false); + tmp.setGroupFlag(ClientGroup.Banned,false); this.sendClientList(); return; } @@ -4980,8 +5008,8 @@ server_Main.prototype = { return; } this.userList.bans.push({ ip : ip, toDate : new Date(time)}); - this.checkBan(bannedClient); - this.serverMessage(client,"" + bannedClient.name + " (" + ip + ") has been banned."); + this.checkBan(tmp); + this.serverMessage(client,"" + tmp.name + " (" + ip + ") has been banned."); this.sendClientList(); break; case "ClearChat": @@ -5022,14 +5050,14 @@ server_Main.prototype = { } } this.checkBan(client); - this.send(client,{ type : "Connected", connected : { config : this.config, history : this.messages, isUnknownClient : true, clientName : client.name, clients : this.clientList(), videoList : this.videoList.items, isPlaylistOpen : this.videoList.isOpen, itemPos : this.videoList.pos, globalIp : this.globalIp}}); + this.send(client,{ type : "Connected", connected : { uuid : client.uuid, config : this.config, history : this.messages, isUnknownClient : true, clientName : client.name, clients : this.clientList(), videoList : this.videoList.items, isPlaylistOpen : this.videoList.isOpen, itemPos : this.videoList.pos, globalIp : this.globalIp}}); this.sendClientListExcept(client); break; case "Disconnected": if(!internal) { return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 462, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 487, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); server_Utils.sortedPush(this.freeIds,client.id); HxOverrides.remove(this.clients,client); this.sendClientList(); @@ -5131,15 +5159,15 @@ server_Main.prototype = { return; } var name = data.kickClient.name; - var kickedClient = ClientTools.getByName(this.clients,name); - if(kickedClient == null) { + var tmp = ClientTools.getByName(this.clients,name); + if(tmp == null) { return; } - if(client.name != name && (kickedClient.group & 8) != 0) { + if(client.name != name && (tmp.group & 8) != 0) { this.serverMessage(client,"adminsCannotBeBannedError"); return; } - this.send(kickedClient,{ type : "KickClient"}); + this.send(tmp,{ type : "KickClient"}); break; case "Login": var name = StringTools.trim(data.login.clientName); @@ -5170,7 +5198,7 @@ server_Main.prototype = { this.send(client,{ type : "LoginError"}); return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 552, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 575, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); client.name = name; client.setGroupFlag(ClientGroup.User,true); this.checkBan(client); @@ -5183,7 +5211,7 @@ server_Main.prototype = { var oldName = client.name; client.name = "Guest " + (this.clients.indexOf(client) + 1); client.setGroupFlag(ClientGroup.User,false); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 573, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 596, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); this.send(client,{ type : data.type, logout : { oldClientName : oldName, clientName : client.name, clients : this.clientList()}}); this.sendClientListExcept(client); break; @@ -5498,7 +5526,7 @@ server_Main.prototype = { client.setGroupFlag(ClientGroup.Banned,!isOutdated); if(isOutdated) { HxOverrides.remove(this.userList.bans,ban); - haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 985, className : "server.Main", methodName : "checkBan"}); + haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 1008, className : "server.Main", methodName : "checkBan"}); this.sendClientList(); } break; diff --git a/package-lock.json b/package-lock.json index ee0a3fd..e229b84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "MIT", "dependencies": { "ws": "^8.17.1" + }, + "engines": { + "node": ">=14.17.0" } }, "node_modules/ws": { diff --git a/package.json b/package.json index bc88ab9..808284b 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,8 @@ "homepage": "https://github.com/RblSb/SyncTube#readme", "dependencies": { "ws": "^8.17.1" + }, + "engines": { + "node": ">=14.17.0" } } diff --git a/res/client.js b/res/client.js index fc9bc48..df2a27d 100644 --- a/res/client.js +++ b/res/client.js @@ -1272,7 +1272,7 @@ var client_Main = function() { if(this.host == "") { this.host = "localhost"; } - client_Settings.init({ version : 4, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true, showHintList : true},$bind(this,this.settingsPatcher)); + client_Settings.init({ version : 4, uuid : null, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true, showHintList : true},$bind(this,this.settingsPatcher)); this.settings = client_Settings.read(); this.initListeners(); this.onTimeGet = new haxe_Timer(this.settings.synchThreshold * 1000); @@ -1353,7 +1353,7 @@ client_Main.prototype = { var port = $global.location.port; var colonPort = port.length > 0 ? ":" + port : port; var path = $global.location.pathname; - this.ws = new WebSocket("" + protocol + "//" + this.host + colonPort + path); + this.ws = new WebSocket("" + protocol + "//" + this.host + colonPort + path + (this.settings.uuid == null ? "" : "?uuid=" + this.settings.uuid)); this.ws.onmessage = $bind(this,this.onMessage); this.ws.onopen = function() { var tmp = _gthis.disconnectNotification; @@ -1609,7 +1609,7 @@ client_Main.prototype = { var data = JSON.parse(e.data); if(this.config != null && this.config.isVerbose) { var t = data.type; - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 418, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 420, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]}); } client_JsApi.fireOnceEvent(data); switch(data.type) { @@ -1805,6 +1805,8 @@ client_Main.prototype = { } ,onConnected: function(data) { var connected = data.connected; + this.settings.uuid = connected.uuid; + client_Settings.write(this.settings); this.globalIp = connected.globalIp; this.setConfig(connected.config); if(connected.isUnknownClient) { diff --git a/src/Client.hx b/src/Client.hx index 64d7e54..dc1a555 100644 --- a/src/Client.hx +++ b/src/Client.hx @@ -25,6 +25,7 @@ class Client { public final ws:WebSocket; public final req:IncomingMessage; public final id:Int; + public var uuid:String; public var isAlive = true; #end public var name:String; diff --git a/src/Types.hx b/src/Types.hx index a02b4a9..0e89b85 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -133,6 +133,7 @@ typedef FlashbackItem = { typedef WsEvent = { type:WsEventType, ?connected:{ + uuid:String, config:Config, history:Array, clients:Array, diff --git a/src/client/ClientSettings.hx b/src/client/ClientSettings.hx index 8403ff3..a004213 100644 --- a/src/client/ClientSettings.hx +++ b/src/client/ClientSettings.hx @@ -2,6 +2,7 @@ package client; typedef ClientSettings = { version:Int, + uuid:Null, name:String, hash:String, isExtendedPlayer:Bool, diff --git a/src/client/Main.hx b/src/client/Main.hx index 8cdd914..6d50b5e 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -61,6 +61,7 @@ class Main { final defaults:ClientSettings = { version: SETTINGS_VERSION, + uuid: null, name: "", hash: "", isExtendedPlayer: false, @@ -124,7 +125,8 @@ class Main { final port = Browser.location.port; final colonPort = port.length > 0 ? ':$port' : port; final path = Browser.location.pathname; - ws = new WebSocket('$protocol//$host$colonPort$path'); + final query = settings.uuid == null ? "" : '?uuid=${settings.uuid}'; + ws = new WebSocket('$protocol//$host$colonPort$path$query'); ws.onmessage = onMessage; ws.onopen = () -> { disconnectNotification?.stop(); @@ -585,6 +587,10 @@ class Main { function onConnected(data:WsEvent):Void { final connected = data.connected; + + settings.uuid = connected.uuid; + Settings.write(settings); + globalIp = connected.globalIp; setConfig(connected.config); if (connected.isUnknownClient) { diff --git a/src/server/Main.hx b/src/server/Main.hx index 2b1aafa..a0b255e 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -13,8 +13,10 @@ import haxe.Timer; import haxe.crypto.Sha256; import js.Node.__dirname; import js.Node.process; +import js.node.Crypto; import js.node.Http; import js.node.http.IncomingMessage; +import js.node.url.URL; import js.npm.ws.Server as WSServer; import js.npm.ws.WebSocket; import json2object.ErrorUtils; @@ -378,13 +380,35 @@ class Main { } } + function randomUuid():String { + return (Crypto : Dynamic).randomUUID(); + } + + function getUrlUuid(link:String):Null { + try { + if (link.startsWith('/')) link = 'http://127.0.0.1$link'; + final url = new URL(link); + return url.searchParams.get("uuid"); + } catch (e) { + return null; + } + } + function onConnect(ws:WebSocket, req:IncomingMessage):Void { + final uuid = getUrlUuid(req.url) ?? randomUuid(); + final oldClient = clients.find(client -> client.uuid == uuid); + if (oldClient != null) { + send(oldClient, {type: KickClient}); + onMessage(oldClient, {type: Disconnected}, true); + } + final ip = clientIp(req); final id = freeIds.length > 0 ? freeIds.shift() : clients.length; final name = 'Guest ${id + 1}'; trace(Date.now().toString(), '$name connected ($ip)'); final isAdmin = config.localAdmins && req.socket.localAddress == ip; final client = new Client(ws, req, id, name, 0); + client.uuid = uuid; client.isAdmin = isAdmin; clients.push(client); ws.on("pong", () -> client.isAlive = true); @@ -444,6 +468,7 @@ class Main { send(client, { type: Connected, connected: { + uuid: client.uuid, config: config, history: messages, isUnknownClient: true, @@ -490,8 +515,7 @@ class Main { case BanClient: if (!checkPermission(client, BanClientPerm)) return; final name = data.banClient.name; - final bannedClient = clients.getByName(name); - if (bannedClient == null) return; + final bannedClient = clients.getByName(name) ?? return; if (client.name == name || bannedClient.isAdmin) { serverMessage(client, "adminsCannotBeBannedError"); return; @@ -517,8 +541,7 @@ class Main { case KickClient: if (!checkPermission(client, BanClientPerm)) return; final name = data.kickClient.name; - final kickedClient = clients.getByName(name); - if (kickedClient == null) return; + final kickedClient = clients.getByName(name) ?? return; if (client.name != name && kickedClient.isAdmin) { serverMessage(client, "adminsCannotBeBannedError"); return;