From 52abf5eb95c9e025807257db56bc4db1d87a753e Mon Sep 17 00:00:00 2001 From: Alessandro Autiero Date: Sat, 9 Aug 2025 02:54:48 +0100 Subject: [PATCH] Renamed `backend` into `auth_backend` and added `server_browser_backend` implementation to replace Supabase. --- .../CloudStorage/DefaultEngine.ini | 0 .../CloudStorage/DefaultGame.ini | 0 .../CloudStorage/DefaultInput.ini | 0 .../CloudStorage/DefaultRuntimeOptions.ini | 0 .../Config/catalog_config.json | 0 {backend => auth_backend}/Config/config.ini | 0 {backend => auth_backend}/LICENSE | 0 {backend => auth_backend}/README.md | 0 {backend => auth_backend}/build.bat | 0 {backend => auth_backend}/index.js | 0 .../install_packages.bat | 0 {backend => auth_backend}/package-lock.json | 0 {backend => auth_backend}/package.json | 0 .../profiles/athena.json | 0 .../profiles/campaign.json | 0 .../profiles/collection_book_people0.json | 0 .../profiles/collection_book_schematics0.json | 0 .../profiles/collections.json | 0 .../profiles/common_core.json | 0 .../profiles/common_public.json | 0 .../profiles/creative.json | 0 .../profiles/metadata.json | 0 .../profiles/outpost0.json | 0 .../profiles/profile0.json | 0 .../profiles/theater0.json | 0 .../public/images/discord-s.png | 0 .../public/images/discord.png | 0 .../public/images/lawin-s.png | 0 .../public/images/lawin.jpg | 0 .../public/images/motd-s.png | 0 .../public/images/motd.png | 0 .../public/images/seasonx.png | 0 .../responses/Athena/BattlePass/Season10.json | 0 .../responses/Athena/BattlePass/Season2.json | 0 .../responses/Athena/BattlePass/Season3.json | 0 .../responses/Athena/BattlePass/Season4.json | 0 .../responses/Athena/BattlePass/Season5.json | 0 .../responses/Athena/BattlePass/Season6.json | 0 .../responses/Athena/BattlePass/Season7.json | 0 .../responses/Athena/BattlePass/Season8.json | 0 .../responses/Athena/BattlePass/Season9.json | 0 .../Discovery/discovery_api_assets.json | 0 .../Athena/Discovery/discovery_frontend.json | 0 .../responses/Athena/SeasonData.json | 0 .../responses/Athena/motdTarget.json | 0 .../responses/Athena/winterfestRewards.json | 0 .../responses/Campaign/cardPackData.json | 0 .../responses/Campaign/dailyRewards.json | 0 .../responses/Campaign/expeditionData.json | 0 .../responses/Campaign/rewards.json | 0 .../responses/Campaign/survivorData.json | 0 .../responses/Campaign/transformItemIDS.json | 0 .../responses/Campaign/worldstw.json | 0 .../responses/CloudDir/Full.ini | 0 .../responses/CloudDir/LawinServer.chunk | 0 .../responses/CloudDir/LawinServer.manifest | 0 {backend => auth_backend}/responses/SAC.json | 0 .../responses/catalog.json | 0 .../responses/contentpages.json | 0 .../responses/friendslist.json | 0 .../responses/friendslist2.json | 0 .../responses/keychain.json | 0 .../responses/privacy.json | 0 .../responses/quests.json | 0 .../responses/sdkv1.json | 0 {backend => auth_backend}/start.bat | 0 .../structure/affiliate.js | 0 .../structure/cloudstorage.js | 0 .../structure/contentpages.js | 0 .../structure/discovery.js | 0 .../structure/friends.js | 0 .../structure/functions.js | 0 .../structure/lightswitch.js | 0 {backend => auth_backend}/structure/main.js | 0 .../structure/matchmaker.js | 0 .../structure/matchmaking.js | 0 {backend => auth_backend}/structure/mcp.js | 0 {backend => auth_backend}/structure/party.js | 0 .../structure/privacy.js | 0 .../structure/storefront.js | 0 .../structure/timeline.js | 0 {backend => auth_backend}/structure/user.js | 0 .../structure/version.js | 0 {backend => auth_backend}/structure/xmpp.js | 0 gui/lib/main.dart | 3 +- gui/lib/src/util/translations.dart | 2 +- gui/lib/src/widget/page/settings_page.dart | 2 +- server_browser_backend/.gitignore | 3 + server_browser_backend/README.md | 2 + server_browser_backend/analysis_options.yaml | 30 ++ server_browser_backend/bin/main.dart | 6 + .../lib/server_browser_backend.dart | 4 + .../lib/src/server_entry.dart | 51 ++ .../lib/src/web_socket.dart | 126 +++++ server_browser_backend/pubspec.yaml | 15 + server_browser_backend/test/test.dart | 437 ++++++++++++++++++ 96 files changed, 678 insertions(+), 3 deletions(-) rename {backend => auth_backend}/CloudStorage/DefaultEngine.ini (100%) rename {backend => auth_backend}/CloudStorage/DefaultGame.ini (100%) rename {backend => auth_backend}/CloudStorage/DefaultInput.ini (100%) rename {backend => auth_backend}/CloudStorage/DefaultRuntimeOptions.ini (100%) rename {backend => auth_backend}/Config/catalog_config.json (100%) rename {backend => auth_backend}/Config/config.ini (100%) rename {backend => auth_backend}/LICENSE (100%) rename {backend => auth_backend}/README.md (100%) rename {backend => auth_backend}/build.bat (100%) rename {backend => auth_backend}/index.js (100%) rename {backend => auth_backend}/install_packages.bat (100%) rename {backend => auth_backend}/package-lock.json (100%) rename {backend => auth_backend}/package.json (100%) rename {backend => auth_backend}/profiles/athena.json (100%) rename {backend => auth_backend}/profiles/campaign.json (100%) rename {backend => auth_backend}/profiles/collection_book_people0.json (100%) rename {backend => auth_backend}/profiles/collection_book_schematics0.json (100%) rename {backend => auth_backend}/profiles/collections.json (100%) rename {backend => auth_backend}/profiles/common_core.json (100%) rename {backend => auth_backend}/profiles/common_public.json (100%) rename {backend => auth_backend}/profiles/creative.json (100%) rename {backend => auth_backend}/profiles/metadata.json (100%) rename {backend => auth_backend}/profiles/outpost0.json (100%) rename {backend => auth_backend}/profiles/profile0.json (100%) rename {backend => auth_backend}/profiles/theater0.json (100%) rename {backend => auth_backend}/public/images/discord-s.png (100%) rename {backend => auth_backend}/public/images/discord.png (100%) rename {backend => auth_backend}/public/images/lawin-s.png (100%) rename {backend => auth_backend}/public/images/lawin.jpg (100%) rename {backend => auth_backend}/public/images/motd-s.png (100%) rename {backend => auth_backend}/public/images/motd.png (100%) rename {backend => auth_backend}/public/images/seasonx.png (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season10.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season2.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season3.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season4.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season5.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season6.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season7.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season8.json (100%) rename {backend => auth_backend}/responses/Athena/BattlePass/Season9.json (100%) rename {backend => auth_backend}/responses/Athena/Discovery/discovery_api_assets.json (100%) rename {backend => auth_backend}/responses/Athena/Discovery/discovery_frontend.json (100%) rename {backend => auth_backend}/responses/Athena/SeasonData.json (100%) rename {backend => auth_backend}/responses/Athena/motdTarget.json (100%) rename {backend => auth_backend}/responses/Athena/winterfestRewards.json (100%) rename {backend => auth_backend}/responses/Campaign/cardPackData.json (100%) rename {backend => auth_backend}/responses/Campaign/dailyRewards.json (100%) rename {backend => auth_backend}/responses/Campaign/expeditionData.json (100%) rename {backend => auth_backend}/responses/Campaign/rewards.json (100%) rename {backend => auth_backend}/responses/Campaign/survivorData.json (100%) rename {backend => auth_backend}/responses/Campaign/transformItemIDS.json (100%) rename {backend => auth_backend}/responses/Campaign/worldstw.json (100%) rename {backend => auth_backend}/responses/CloudDir/Full.ini (100%) rename {backend => auth_backend}/responses/CloudDir/LawinServer.chunk (100%) rename {backend => auth_backend}/responses/CloudDir/LawinServer.manifest (100%) rename {backend => auth_backend}/responses/SAC.json (100%) rename {backend => auth_backend}/responses/catalog.json (100%) rename {backend => auth_backend}/responses/contentpages.json (100%) rename {backend => auth_backend}/responses/friendslist.json (100%) rename {backend => auth_backend}/responses/friendslist2.json (100%) rename {backend => auth_backend}/responses/keychain.json (100%) rename {backend => auth_backend}/responses/privacy.json (100%) rename {backend => auth_backend}/responses/quests.json (100%) rename {backend => auth_backend}/responses/sdkv1.json (100%) rename {backend => auth_backend}/start.bat (100%) rename {backend => auth_backend}/structure/affiliate.js (100%) rename {backend => auth_backend}/structure/cloudstorage.js (100%) rename {backend => auth_backend}/structure/contentpages.js (100%) rename {backend => auth_backend}/structure/discovery.js (100%) rename {backend => auth_backend}/structure/friends.js (100%) rename {backend => auth_backend}/structure/functions.js (100%) rename {backend => auth_backend}/structure/lightswitch.js (100%) rename {backend => auth_backend}/structure/main.js (100%) rename {backend => auth_backend}/structure/matchmaker.js (100%) rename {backend => auth_backend}/structure/matchmaking.js (100%) rename {backend => auth_backend}/structure/mcp.js (100%) rename {backend => auth_backend}/structure/party.js (100%) rename {backend => auth_backend}/structure/privacy.js (100%) rename {backend => auth_backend}/structure/storefront.js (100%) rename {backend => auth_backend}/structure/timeline.js (100%) rename {backend => auth_backend}/structure/user.js (100%) rename {backend => auth_backend}/structure/version.js (100%) rename {backend => auth_backend}/structure/xmpp.js (100%) create mode 100644 server_browser_backend/.gitignore create mode 100644 server_browser_backend/README.md create mode 100644 server_browser_backend/analysis_options.yaml create mode 100644 server_browser_backend/bin/main.dart create mode 100644 server_browser_backend/lib/server_browser_backend.dart create mode 100644 server_browser_backend/lib/src/server_entry.dart create mode 100644 server_browser_backend/lib/src/web_socket.dart create mode 100644 server_browser_backend/pubspec.yaml create mode 100644 server_browser_backend/test/test.dart diff --git a/backend/CloudStorage/DefaultEngine.ini b/auth_backend/CloudStorage/DefaultEngine.ini similarity index 100% rename from backend/CloudStorage/DefaultEngine.ini rename to auth_backend/CloudStorage/DefaultEngine.ini diff --git a/backend/CloudStorage/DefaultGame.ini b/auth_backend/CloudStorage/DefaultGame.ini similarity index 100% rename from backend/CloudStorage/DefaultGame.ini rename to auth_backend/CloudStorage/DefaultGame.ini diff --git a/backend/CloudStorage/DefaultInput.ini b/auth_backend/CloudStorage/DefaultInput.ini similarity index 100% rename from backend/CloudStorage/DefaultInput.ini rename to auth_backend/CloudStorage/DefaultInput.ini diff --git a/backend/CloudStorage/DefaultRuntimeOptions.ini b/auth_backend/CloudStorage/DefaultRuntimeOptions.ini similarity index 100% rename from backend/CloudStorage/DefaultRuntimeOptions.ini rename to auth_backend/CloudStorage/DefaultRuntimeOptions.ini diff --git a/backend/Config/catalog_config.json b/auth_backend/Config/catalog_config.json similarity index 100% rename from backend/Config/catalog_config.json rename to auth_backend/Config/catalog_config.json diff --git a/backend/Config/config.ini b/auth_backend/Config/config.ini similarity index 100% rename from backend/Config/config.ini rename to auth_backend/Config/config.ini diff --git a/backend/LICENSE b/auth_backend/LICENSE similarity index 100% rename from backend/LICENSE rename to auth_backend/LICENSE diff --git a/backend/README.md b/auth_backend/README.md similarity index 100% rename from backend/README.md rename to auth_backend/README.md diff --git a/backend/build.bat b/auth_backend/build.bat similarity index 100% rename from backend/build.bat rename to auth_backend/build.bat diff --git a/backend/index.js b/auth_backend/index.js similarity index 100% rename from backend/index.js rename to auth_backend/index.js diff --git a/backend/install_packages.bat b/auth_backend/install_packages.bat similarity index 100% rename from backend/install_packages.bat rename to auth_backend/install_packages.bat diff --git a/backend/package-lock.json b/auth_backend/package-lock.json similarity index 100% rename from backend/package-lock.json rename to auth_backend/package-lock.json diff --git a/backend/package.json b/auth_backend/package.json similarity index 100% rename from backend/package.json rename to auth_backend/package.json diff --git a/backend/profiles/athena.json b/auth_backend/profiles/athena.json similarity index 100% rename from backend/profiles/athena.json rename to auth_backend/profiles/athena.json diff --git a/backend/profiles/campaign.json b/auth_backend/profiles/campaign.json similarity index 100% rename from backend/profiles/campaign.json rename to auth_backend/profiles/campaign.json diff --git a/backend/profiles/collection_book_people0.json b/auth_backend/profiles/collection_book_people0.json similarity index 100% rename from backend/profiles/collection_book_people0.json rename to auth_backend/profiles/collection_book_people0.json diff --git a/backend/profiles/collection_book_schematics0.json b/auth_backend/profiles/collection_book_schematics0.json similarity index 100% rename from backend/profiles/collection_book_schematics0.json rename to auth_backend/profiles/collection_book_schematics0.json diff --git a/backend/profiles/collections.json b/auth_backend/profiles/collections.json similarity index 100% rename from backend/profiles/collections.json rename to auth_backend/profiles/collections.json diff --git a/backend/profiles/common_core.json b/auth_backend/profiles/common_core.json similarity index 100% rename from backend/profiles/common_core.json rename to auth_backend/profiles/common_core.json diff --git a/backend/profiles/common_public.json b/auth_backend/profiles/common_public.json similarity index 100% rename from backend/profiles/common_public.json rename to auth_backend/profiles/common_public.json diff --git a/backend/profiles/creative.json b/auth_backend/profiles/creative.json similarity index 100% rename from backend/profiles/creative.json rename to auth_backend/profiles/creative.json diff --git a/backend/profiles/metadata.json b/auth_backend/profiles/metadata.json similarity index 100% rename from backend/profiles/metadata.json rename to auth_backend/profiles/metadata.json diff --git a/backend/profiles/outpost0.json b/auth_backend/profiles/outpost0.json similarity index 100% rename from backend/profiles/outpost0.json rename to auth_backend/profiles/outpost0.json diff --git a/backend/profiles/profile0.json b/auth_backend/profiles/profile0.json similarity index 100% rename from backend/profiles/profile0.json rename to auth_backend/profiles/profile0.json diff --git a/backend/profiles/theater0.json b/auth_backend/profiles/theater0.json similarity index 100% rename from backend/profiles/theater0.json rename to auth_backend/profiles/theater0.json diff --git a/backend/public/images/discord-s.png b/auth_backend/public/images/discord-s.png similarity index 100% rename from backend/public/images/discord-s.png rename to auth_backend/public/images/discord-s.png diff --git a/backend/public/images/discord.png b/auth_backend/public/images/discord.png similarity index 100% rename from backend/public/images/discord.png rename to auth_backend/public/images/discord.png diff --git a/backend/public/images/lawin-s.png b/auth_backend/public/images/lawin-s.png similarity index 100% rename from backend/public/images/lawin-s.png rename to auth_backend/public/images/lawin-s.png diff --git a/backend/public/images/lawin.jpg b/auth_backend/public/images/lawin.jpg similarity index 100% rename from backend/public/images/lawin.jpg rename to auth_backend/public/images/lawin.jpg diff --git a/backend/public/images/motd-s.png b/auth_backend/public/images/motd-s.png similarity index 100% rename from backend/public/images/motd-s.png rename to auth_backend/public/images/motd-s.png diff --git a/backend/public/images/motd.png b/auth_backend/public/images/motd.png similarity index 100% rename from backend/public/images/motd.png rename to auth_backend/public/images/motd.png diff --git a/backend/public/images/seasonx.png b/auth_backend/public/images/seasonx.png similarity index 100% rename from backend/public/images/seasonx.png rename to auth_backend/public/images/seasonx.png diff --git a/backend/responses/Athena/BattlePass/Season10.json b/auth_backend/responses/Athena/BattlePass/Season10.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season10.json rename to auth_backend/responses/Athena/BattlePass/Season10.json diff --git a/backend/responses/Athena/BattlePass/Season2.json b/auth_backend/responses/Athena/BattlePass/Season2.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season2.json rename to auth_backend/responses/Athena/BattlePass/Season2.json diff --git a/backend/responses/Athena/BattlePass/Season3.json b/auth_backend/responses/Athena/BattlePass/Season3.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season3.json rename to auth_backend/responses/Athena/BattlePass/Season3.json diff --git a/backend/responses/Athena/BattlePass/Season4.json b/auth_backend/responses/Athena/BattlePass/Season4.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season4.json rename to auth_backend/responses/Athena/BattlePass/Season4.json diff --git a/backend/responses/Athena/BattlePass/Season5.json b/auth_backend/responses/Athena/BattlePass/Season5.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season5.json rename to auth_backend/responses/Athena/BattlePass/Season5.json diff --git a/backend/responses/Athena/BattlePass/Season6.json b/auth_backend/responses/Athena/BattlePass/Season6.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season6.json rename to auth_backend/responses/Athena/BattlePass/Season6.json diff --git a/backend/responses/Athena/BattlePass/Season7.json b/auth_backend/responses/Athena/BattlePass/Season7.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season7.json rename to auth_backend/responses/Athena/BattlePass/Season7.json diff --git a/backend/responses/Athena/BattlePass/Season8.json b/auth_backend/responses/Athena/BattlePass/Season8.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season8.json rename to auth_backend/responses/Athena/BattlePass/Season8.json diff --git a/backend/responses/Athena/BattlePass/Season9.json b/auth_backend/responses/Athena/BattlePass/Season9.json similarity index 100% rename from backend/responses/Athena/BattlePass/Season9.json rename to auth_backend/responses/Athena/BattlePass/Season9.json diff --git a/backend/responses/Athena/Discovery/discovery_api_assets.json b/auth_backend/responses/Athena/Discovery/discovery_api_assets.json similarity index 100% rename from backend/responses/Athena/Discovery/discovery_api_assets.json rename to auth_backend/responses/Athena/Discovery/discovery_api_assets.json diff --git a/backend/responses/Athena/Discovery/discovery_frontend.json b/auth_backend/responses/Athena/Discovery/discovery_frontend.json similarity index 100% rename from backend/responses/Athena/Discovery/discovery_frontend.json rename to auth_backend/responses/Athena/Discovery/discovery_frontend.json diff --git a/backend/responses/Athena/SeasonData.json b/auth_backend/responses/Athena/SeasonData.json similarity index 100% rename from backend/responses/Athena/SeasonData.json rename to auth_backend/responses/Athena/SeasonData.json diff --git a/backend/responses/Athena/motdTarget.json b/auth_backend/responses/Athena/motdTarget.json similarity index 100% rename from backend/responses/Athena/motdTarget.json rename to auth_backend/responses/Athena/motdTarget.json diff --git a/backend/responses/Athena/winterfestRewards.json b/auth_backend/responses/Athena/winterfestRewards.json similarity index 100% rename from backend/responses/Athena/winterfestRewards.json rename to auth_backend/responses/Athena/winterfestRewards.json diff --git a/backend/responses/Campaign/cardPackData.json b/auth_backend/responses/Campaign/cardPackData.json similarity index 100% rename from backend/responses/Campaign/cardPackData.json rename to auth_backend/responses/Campaign/cardPackData.json diff --git a/backend/responses/Campaign/dailyRewards.json b/auth_backend/responses/Campaign/dailyRewards.json similarity index 100% rename from backend/responses/Campaign/dailyRewards.json rename to auth_backend/responses/Campaign/dailyRewards.json diff --git a/backend/responses/Campaign/expeditionData.json b/auth_backend/responses/Campaign/expeditionData.json similarity index 100% rename from backend/responses/Campaign/expeditionData.json rename to auth_backend/responses/Campaign/expeditionData.json diff --git a/backend/responses/Campaign/rewards.json b/auth_backend/responses/Campaign/rewards.json similarity index 100% rename from backend/responses/Campaign/rewards.json rename to auth_backend/responses/Campaign/rewards.json diff --git a/backend/responses/Campaign/survivorData.json b/auth_backend/responses/Campaign/survivorData.json similarity index 100% rename from backend/responses/Campaign/survivorData.json rename to auth_backend/responses/Campaign/survivorData.json diff --git a/backend/responses/Campaign/transformItemIDS.json b/auth_backend/responses/Campaign/transformItemIDS.json similarity index 100% rename from backend/responses/Campaign/transformItemIDS.json rename to auth_backend/responses/Campaign/transformItemIDS.json diff --git a/backend/responses/Campaign/worldstw.json b/auth_backend/responses/Campaign/worldstw.json similarity index 100% rename from backend/responses/Campaign/worldstw.json rename to auth_backend/responses/Campaign/worldstw.json diff --git a/backend/responses/CloudDir/Full.ini b/auth_backend/responses/CloudDir/Full.ini similarity index 100% rename from backend/responses/CloudDir/Full.ini rename to auth_backend/responses/CloudDir/Full.ini diff --git a/backend/responses/CloudDir/LawinServer.chunk b/auth_backend/responses/CloudDir/LawinServer.chunk similarity index 100% rename from backend/responses/CloudDir/LawinServer.chunk rename to auth_backend/responses/CloudDir/LawinServer.chunk diff --git a/backend/responses/CloudDir/LawinServer.manifest b/auth_backend/responses/CloudDir/LawinServer.manifest similarity index 100% rename from backend/responses/CloudDir/LawinServer.manifest rename to auth_backend/responses/CloudDir/LawinServer.manifest diff --git a/backend/responses/SAC.json b/auth_backend/responses/SAC.json similarity index 100% rename from backend/responses/SAC.json rename to auth_backend/responses/SAC.json diff --git a/backend/responses/catalog.json b/auth_backend/responses/catalog.json similarity index 100% rename from backend/responses/catalog.json rename to auth_backend/responses/catalog.json diff --git a/backend/responses/contentpages.json b/auth_backend/responses/contentpages.json similarity index 100% rename from backend/responses/contentpages.json rename to auth_backend/responses/contentpages.json diff --git a/backend/responses/friendslist.json b/auth_backend/responses/friendslist.json similarity index 100% rename from backend/responses/friendslist.json rename to auth_backend/responses/friendslist.json diff --git a/backend/responses/friendslist2.json b/auth_backend/responses/friendslist2.json similarity index 100% rename from backend/responses/friendslist2.json rename to auth_backend/responses/friendslist2.json diff --git a/backend/responses/keychain.json b/auth_backend/responses/keychain.json similarity index 100% rename from backend/responses/keychain.json rename to auth_backend/responses/keychain.json diff --git a/backend/responses/privacy.json b/auth_backend/responses/privacy.json similarity index 100% rename from backend/responses/privacy.json rename to auth_backend/responses/privacy.json diff --git a/backend/responses/quests.json b/auth_backend/responses/quests.json similarity index 100% rename from backend/responses/quests.json rename to auth_backend/responses/quests.json diff --git a/backend/responses/sdkv1.json b/auth_backend/responses/sdkv1.json similarity index 100% rename from backend/responses/sdkv1.json rename to auth_backend/responses/sdkv1.json diff --git a/backend/start.bat b/auth_backend/start.bat similarity index 100% rename from backend/start.bat rename to auth_backend/start.bat diff --git a/backend/structure/affiliate.js b/auth_backend/structure/affiliate.js similarity index 100% rename from backend/structure/affiliate.js rename to auth_backend/structure/affiliate.js diff --git a/backend/structure/cloudstorage.js b/auth_backend/structure/cloudstorage.js similarity index 100% rename from backend/structure/cloudstorage.js rename to auth_backend/structure/cloudstorage.js diff --git a/backend/structure/contentpages.js b/auth_backend/structure/contentpages.js similarity index 100% rename from backend/structure/contentpages.js rename to auth_backend/structure/contentpages.js diff --git a/backend/structure/discovery.js b/auth_backend/structure/discovery.js similarity index 100% rename from backend/structure/discovery.js rename to auth_backend/structure/discovery.js diff --git a/backend/structure/friends.js b/auth_backend/structure/friends.js similarity index 100% rename from backend/structure/friends.js rename to auth_backend/structure/friends.js diff --git a/backend/structure/functions.js b/auth_backend/structure/functions.js similarity index 100% rename from backend/structure/functions.js rename to auth_backend/structure/functions.js diff --git a/backend/structure/lightswitch.js b/auth_backend/structure/lightswitch.js similarity index 100% rename from backend/structure/lightswitch.js rename to auth_backend/structure/lightswitch.js diff --git a/backend/structure/main.js b/auth_backend/structure/main.js similarity index 100% rename from backend/structure/main.js rename to auth_backend/structure/main.js diff --git a/backend/structure/matchmaker.js b/auth_backend/structure/matchmaker.js similarity index 100% rename from backend/structure/matchmaker.js rename to auth_backend/structure/matchmaker.js diff --git a/backend/structure/matchmaking.js b/auth_backend/structure/matchmaking.js similarity index 100% rename from backend/structure/matchmaking.js rename to auth_backend/structure/matchmaking.js diff --git a/backend/structure/mcp.js b/auth_backend/structure/mcp.js similarity index 100% rename from backend/structure/mcp.js rename to auth_backend/structure/mcp.js diff --git a/backend/structure/party.js b/auth_backend/structure/party.js similarity index 100% rename from backend/structure/party.js rename to auth_backend/structure/party.js diff --git a/backend/structure/privacy.js b/auth_backend/structure/privacy.js similarity index 100% rename from backend/structure/privacy.js rename to auth_backend/structure/privacy.js diff --git a/backend/structure/storefront.js b/auth_backend/structure/storefront.js similarity index 100% rename from backend/structure/storefront.js rename to auth_backend/structure/storefront.js diff --git a/backend/structure/timeline.js b/auth_backend/structure/timeline.js similarity index 100% rename from backend/structure/timeline.js rename to auth_backend/structure/timeline.js diff --git a/backend/structure/user.js b/auth_backend/structure/user.js similarity index 100% rename from backend/structure/user.js rename to auth_backend/structure/user.js diff --git a/backend/structure/version.js b/auth_backend/structure/version.js similarity index 100% rename from backend/structure/version.js rename to auth_backend/structure/version.js diff --git a/backend/structure/xmpp.js b/auth_backend/structure/xmpp.js similarity index 100% rename from backend/structure/xmpp.js rename to auth_backend/structure/xmpp.js diff --git a/gui/lib/main.dart b/gui/lib/main.dart index d70f19d..ec6b329 100644 --- a/gui/lib/main.dart +++ b/gui/lib/main.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; -import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; @@ -24,6 +23,8 @@ import 'package:system_theme/system_theme.dart'; import 'package:version/version.dart'; import 'package:window_manager/window_manager.dart'; +import 'l10n/reboot_localizations.dart'; + const double kDefaultWindowWidth = 1164; const double kDefaultWindowHeight = 864; const String kCustomUrlSchema = "Reboot"; diff --git a/gui/lib/src/util/translations.dart b/gui/lib/src/util/translations.dart index fc5c946..4b3e87a 100644 --- a/gui/lib/src/util/translations.dart +++ b/gui/lib/src/util/translations.dart @@ -1,6 +1,6 @@ import 'package:fluent_ui/fluent_ui.dart'; -import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:reboot_launcher/l10n/reboot_localizations.dart'; AppLocalizations? _translations; bool _init = false; diff --git a/gui/lib/src/widget/page/settings_page.dart b/gui/lib/src/widget/page/settings_page.dart index f811e1a..36651f7 100644 --- a/gui/lib/src/widget/page/settings_page.dart +++ b/gui/lib/src/widget/page/settings_page.dart @@ -3,10 +3,10 @@ import 'dart:math'; import 'package:async/async.dart'; import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; -import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; +import 'package:reboot_launcher/l10n/reboot_localizations.dart'; import 'package:reboot_launcher/src/controller/dll_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/messenger/dialog.dart'; diff --git a/server_browser_backend/.gitignore b/server_browser_backend/.gitignore new file mode 100644 index 0000000..3a85790 --- /dev/null +++ b/server_browser_backend/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/server_browser_backend/README.md b/server_browser_backend/README.md new file mode 100644 index 0000000..3816eca --- /dev/null +++ b/server_browser_backend/README.md @@ -0,0 +1,2 @@ +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`, and example unit test in `test/`. diff --git a/server_browser_backend/analysis_options.yaml b/server_browser_backend/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/server_browser_backend/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/server_browser_backend/bin/main.dart b/server_browser_backend/bin/main.dart new file mode 100644 index 0000000..38d27dd --- /dev/null +++ b/server_browser_backend/bin/main.dart @@ -0,0 +1,6 @@ +import 'package:server_browser_backend/server_browser_backend.dart'; + +void main() async { + final server = WebSocketServer(); + await server.start(port: 8080); +} \ No newline at end of file diff --git a/server_browser_backend/lib/server_browser_backend.dart b/server_browser_backend/lib/server_browser_backend.dart new file mode 100644 index 0000000..aea390a --- /dev/null +++ b/server_browser_backend/lib/server_browser_backend.dart @@ -0,0 +1,4 @@ +library; + +export 'src/server_entry.dart'; +export 'src/web_socket.dart'; \ No newline at end of file diff --git a/server_browser_backend/lib/src/server_entry.dart b/server_browser_backend/lib/src/server_entry.dart new file mode 100644 index 0000000..b3cba4c --- /dev/null +++ b/server_browser_backend/lib/src/server_entry.dart @@ -0,0 +1,51 @@ +class ServerEntry { + final String id; + final String name; + final String description; + final String version; + final String password; + final DateTime timestamp; + final String ip; + final String author; + final bool discoverable; + + ServerEntry({ + required this.id, + required this.name, + required this.description, + required this.version, + required this.password, + required this.timestamp, + required this.ip, + required this.author, + required this.discoverable, + }); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'description': description, + 'version': version, + 'password': password, + 'timestamp': timestamp.toIso8601String(), + 'ip': ip, + 'author': author, + 'discoverable': discoverable, + }; + } + + static ServerEntry fromJson(Map json) { + return ServerEntry( + id: json['id'], + name: json['name'], + description: json['description'], + version: json['version'], + password: json['password'], + timestamp: DateTime.parse(json['timestamp']), + ip: json['ip'], + author: json['author'], + discoverable: json['discoverable'], + ); + } +} \ No newline at end of file diff --git a/server_browser_backend/lib/src/web_socket.dart b/server_browser_backend/lib/src/web_socket.dart new file mode 100644 index 0000000..6ed129c --- /dev/null +++ b/server_browser_backend/lib/src/web_socket.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:server_browser_backend/src/server_entry.dart'; + +class WebSocketServer { + static const String addEvent = 'add'; + static const String removeEvent = 'remove'; + + final Map _entries = {}; + final Set _clients = {}; + + late HttpServer _server; + + Future start({int port = 8080}) async { + _server = await HttpServer.bind('0.0.0.0', port); + _listen(); + } + + Future _listen() async { + await for (HttpRequest request in _server) { + if (WebSocketTransformer.isUpgradeRequest(request)) { + _handleWebSocketUpgrade(request); + } else { + request.response.statusCode = 404; + request.response.close(); + } + } + } + + Future _handleWebSocketUpgrade(HttpRequest request) async { + final client = await WebSocketTransformer.upgrade(request); + _clients.add(client); + await _sendAllEntriesToClient(client); + client.listen( + (message) => _handleMessage(client, message), + onDone: () => _removeClient(client), + onError: (error) => _removeClient(client) + ); + } + + Future _sendAllEntriesToClient(WebSocket client) async { + final message = { + 'type': addEvent, + 'data': _entries.values.map((entry) => entry.toJson()).toList() + }; + client.add(json.encode(message)); + } + + void _handleMessage(WebSocket client, dynamic message) { + String? type; + try { + final data = jsonDecode(message); + type = data['type']; + final payload = data['data']; + switch (type) { + case addEvent: + final entry = ServerEntry.fromJson(payload); + _entries[entry.id] = entry; + _broadcastEvent(addEvent, entry.toJson()); + break; + case removeEvent: + final deletedEntry = _entries.remove(payload); + if (deletedEntry != null) { + _broadcastEvent(removeEvent, {'id': deletedEntry.id}); + }else { + _answer(client, removeEvent, "Invalid server entry"); + } + break; + default: + _answer(client, type, 'Unknown type'); + break; + } + } catch(error) { + _answer(client, type, error.toString()); + } + } + + void _broadcastEvent(String eventType, Map eventData) { + final message = { + 'type': eventType, + 'data': [ + eventData + ] + }; + + final messageJson = json.encode(message); + final clientsToRemove = []; + + for (final client in _clients) { + try { + client.add(messageJson); + } catch (e) { + clientsToRemove.add(client); + } + } + + for (final client in clientsToRemove) { + _removeClient(client); + } + } + + void _removeClient(WebSocket client) { + client.close(); + _clients.remove(client); + } + + void _answer(WebSocket client, String? eventType, [String? error]) { + final message = {}; + if(eventType != null) { + message['type'] = eventType; + } + if(error != null) { + message['success'] = false; + message['message'] = error; + }else { + message['success'] = true; + } + client.add(json.encode(message)); + } + + Future stop() async { + await _server.close(force: true); + _clients.clear(); + } +} \ No newline at end of file diff --git a/server_browser_backend/pubspec.yaml b/server_browser_backend/pubspec.yaml new file mode 100644 index 0000000..ffe0062 --- /dev/null +++ b/server_browser_backend/pubspec.yaml @@ -0,0 +1,15 @@ +name: server_browser_backend +description: A sample command-line application. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.8.1 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^5.0.0 + test: ^1.24.0 diff --git a/server_browser_backend/test/test.dart b/server_browser_backend/test/test.dart new file mode 100644 index 0000000..c8dd961 --- /dev/null +++ b/server_browser_backend/test/test.dart @@ -0,0 +1,437 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:server_browser_backend/server_browser_backend.dart'; +import 'package:test/test.dart'; + +void main() { + group('WebSocket Server Tests', () { + late WebSocketServer server; + final int testPort = 8081; + + setUp(() async { + server = WebSocketServer(); + await server.start(port: testPort); + }); + + tearDown(() async { + await server.stop(); + }); + + test('should allow client connection and receive initial empty data', () async { + final client = await WebSocket.connect('ws://localhost:$testPort'); + final messagesFuture = client.toList(); + + await client.close(); + + final messages = await messagesFuture; + + expect(messages.length, equals(1)); + + final firstMessage = jsonDecode(messages[0]); + expect(firstMessage['type'], equals('add')); + expect(firstMessage['data'], equals([])); + }); + + test('should add server entry and broadcast to all clients', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + + final testEntry = { + 'id': 'test-server-1', + 'name': 'Test Server', + 'description': 'A test server', + 'version': '1.0.0', + 'password': 'secret123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '127.0.0.1', + 'author': 'Test Author', + 'discoverable': true, + }; + + final addMessage = { + 'type': 'add', + 'data': testEntry, + }; + + client1.add(jsonEncode(addMessage)); + + await client1.close(); + await client2.close(); + + final client1Messages = await client1MessagesFuture; + final client2Messages = await client2MessagesFuture; + + expect(client1Messages.length, equals(2)); + expect(client2Messages.length, equals(2)); + + final client1Initial = jsonDecode(client1Messages[0]); + final client2Initial = jsonDecode(client2Messages[0]); + expect(client1Initial['type'], equals('add')); + expect(client1Initial['data'], equals([])); + expect(client2Initial['type'], equals('add')); + expect(client2Initial['data'], equals([])); + + final client1Broadcast = jsonDecode(client1Messages[1]); + final client2Broadcast = jsonDecode(client2Messages[1]); + + expect(client1Broadcast['type'], equals('add')); + expect(client1Broadcast['data'], isA()); + expect(client1Broadcast['data'].length, equals(1)); + expect(client1Broadcast['data'][0]['id'], equals('test-server-1')); + + expect(client2Broadcast['type'], equals('add')); + expect(client2Broadcast['data'], isA()); + expect(client2Broadcast['data'].length, equals(1)); + expect(client2Broadcast['data'][0]['id'], equals('test-server-1')); + }); + + test('should send existing entries to new client', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + + final testEntry = { + 'id': 'existing-server', + 'name': 'Existing Server', + 'description': 'Already exists', + 'version': '2.0.0', + 'password': 'existing123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '192.168.1.1', + 'author': 'Existing Author', + 'discoverable': false, + }; + + final addMessage = { + 'type': 'add', + 'data': testEntry, + }; + + client1.add(jsonEncode(addMessage)); + + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + + await client1.close(); + await client2.close(); + + final client1Messages = await client1MessagesFuture; + final client2Messages = await client2MessagesFuture; + + expect(client1Messages.length, equals(2)); + expect(client2Messages.length, equals(1)); + + final client2InitialMessage = jsonDecode(client2Messages[0]); + expect(client2InitialMessage['type'], equals('add')); + expect(client2InitialMessage['data'], isA()); + expect(client2InitialMessage['data'].length, equals(1)); + expect(client2InitialMessage['data'][0]['id'], equals('existing-server')); + }); + + test('should remove server entry and broadcast removal', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + + final testEntry = { + 'id': 'server-to-remove', + 'name': 'Server To Remove', + 'description': 'Will be removed', + 'version': '1.0.0', + 'password': 'remove123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '10.0.0.1', + 'author': 'Remove Author', + 'discoverable': true, + }; + + final addMessage = { + 'type': 'add', + 'data': testEntry, + }; + client1.add(jsonEncode(addMessage)); + + final removeMessage = { + 'type': 'remove', + 'data': 'server-to-remove', + }; + client1.add(jsonEncode(removeMessage)); + + await client1.close(); + await client2.close(); + + final client1Messages = await client1MessagesFuture; + final client2Messages = await client2MessagesFuture; + + expect(client1Messages.length, equals(3)); + expect(client2Messages.length, equals(3)); + + final client1RemoveResponse = jsonDecode(client1Messages[2]); + final client2RemoveResponse = jsonDecode(client2Messages[2]); + + expect(client1RemoveResponse['type'], equals('remove')); + expect(client1RemoveResponse['data'], isA()); + expect(client1RemoveResponse['data'].length, equals(1)); + expect(client1RemoveResponse['data'][0]['id'], equals('server-to-remove')); + + expect(client2RemoveResponse['type'], equals('remove')); + expect(client2RemoveResponse['data'], isA()); + expect(client2RemoveResponse['data'].length, equals(1)); + expect(client2RemoveResponse['data'][0]['id'], equals('server-to-remove')); + }); + + test('should handle removal of non-existent entry', () async { + final client = await WebSocket.connect('ws://localhost:$testPort'); + final messagesFuture = client.toList(); + + final removeMessage = { + 'type': 'remove', + 'data': 'non-existent-id', + }; + + client.add(jsonEncode(removeMessage)); + + await client.close(); + final messages = await messagesFuture; + + expect(messages.length, equals(2)); + + final response = jsonDecode(messages[1]); + expect(response['type'], equals('remove')); + expect(response['success'], equals(false)); + expect(response['message'], equals('Invalid server entry')); + }); + + test('should handle unknown message type', () async { + final client = await WebSocket.connect('ws://localhost:$testPort'); + final messagesFuture = client.toList(); + + final unknownMessage = { + 'type': 'unknown', + 'data': {'some': 'data'}, + }; + + client.add(jsonEncode(unknownMessage)); + + await client.close(); + final messages = await messagesFuture; + + expect(messages.length, equals(2)); + + final response = jsonDecode(messages[1]); + expect(response['type'], equals('unknown')); + expect(response['success'], equals(false)); + expect(response['message'], equals('Unknown type')); + }); + + test('should handle invalid JSON', () async { + final client = await WebSocket.connect('ws://localhost:$testPort'); + final messagesFuture = client.toList(); + + client.add('invalid json string'); + + await client.close(); + final messages = await messagesFuture; + + expect(messages.length, equals(2)); + + final response = jsonDecode(messages[1]); + expect(response['success'], equals(false)); + expect(response.containsKey('message'), isTrue); + }); + + test('should handle multiple clients adding different entries', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + final client3 = await WebSocket.connect('ws://localhost:$testPort'); + final client3MessagesFuture = client3.toList(); + + final entry1 = { + 'id': 'server-1', + 'name': 'Server One', + 'description': 'First server', + 'version': '1.0.0', + 'password': 'pass1', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '192.168.1.1', + 'author': 'Author 1', + 'discoverable': true, + }; + + final entry2 = { + 'id': 'server-2', + 'name': 'Server Two', + 'description': 'Second server', + 'version': '2.0.0', + 'password': 'pass2', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '192.168.1.2', + 'author': 'Author 2', + 'discoverable': false, + }; + + client1.add(jsonEncode({'type': 'add', 'data': entry1})); + client2.add(jsonEncode({'type': 'add', 'data': entry2})); + + await Future.delayed(Duration(milliseconds: 200)); + + await client1.close(); + await client2.close(); + await client3.close(); + + final client1Messages = await client1MessagesFuture; + final client2Messages = await client2MessagesFuture; + final client3Messages = await client3MessagesFuture; + + expect(client1Messages.length, equals(3)); + expect(client2Messages.length, equals(3)); + expect(client3Messages.length, equals(3)); + + final allServerIds = {}; + + for (final messages in [client1Messages, client2Messages, client3Messages]) { + for (int i = 1; i < messages.length; i++) { + final parsed = jsonDecode(messages[i]); + if (parsed['type'] == 'add' && parsed['data'] is List) { + for (final entry in parsed['data']) { + allServerIds.add(entry['id']); + } + } + } + } + + expect(allServerIds, containsAll(['server-1', 'server-2'])); + }); + + test('should handle client disconnection gracefully', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + + await client1.close(); + final client1Messages = await client1MessagesFuture; + expect(client1Messages.length, equals(1)); + + final testEntry = { + 'id': 'after-disconnect', + 'name': 'After Disconnect', + 'description': 'Added after client disconnect', + 'version': '1.0.0', + 'password': 'disconnect123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '172.16.0.1', + 'author': 'Disconnect Author', + 'discoverable': true, + }; + + client2.add(jsonEncode({'type': 'add', 'data': testEntry})); + + await client2.close(); + final client2Messages = await client2MessagesFuture; + + expect(client2Messages.length, equals(2)); + + final response = jsonDecode(client2Messages[1]); + expect(response['type'], equals('add')); + expect(response['data'][0]['id'], equals('after-disconnect')); + }); + + test('should handle ServerEntry serialization correctly', () async { + final client = await WebSocket.connect('ws://localhost:$testPort'); + final messagesFuture = client.toList(); + + final testEntry = { + 'id': 'serialization-test', + 'name': 'Serialization Test', + 'description': 'Testing serialization', + 'version': '3.1.4', + 'password': 'serialize123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '203.0.113.1', + 'author': 'Serialization Author', + 'discoverable': true, + }; + + client.add(jsonEncode({'type': 'add', 'data': testEntry})); + + await client.close(); + final messages = await messagesFuture; + + expect(messages.length, equals(2)); + + final broadcastMessage = jsonDecode(messages[1]); + final receivedEntry = broadcastMessage['data'][0]; + + expect(receivedEntry['id'], equals('serialization-test')); + expect(receivedEntry['name'], equals('Serialization Test')); + expect(receivedEntry['description'], equals('Testing serialization')); + expect(receivedEntry['version'], equals('3.1.4')); + expect(receivedEntry['password'], equals('serialize123')); + expect(receivedEntry['ip'], equals('203.0.113.1')); + expect(receivedEntry['author'], equals('Serialization Author')); + expect(receivedEntry['discoverable'], equals(true)); + expect(receivedEntry['timestamp'], isA()); + + expect(() => DateTime.parse(receivedEntry['timestamp']), returnsNormally); + }); + + test('should handle rapid sequential operations', () async { + final client1 = await WebSocket.connect('ws://localhost:$testPort'); + final client1MessagesFuture = client1.toList(); + final client2 = await WebSocket.connect('ws://localhost:$testPort'); + final client2MessagesFuture = client2.toList(); + + final entry1 = { + 'id': 'rapid-1', + 'name': 'Rapid Server 1', + 'description': 'First rapid server', + 'version': '1.0.0', + 'password': 'rapid123', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '10.0.0.1', + 'author': 'Rapid Author', + 'discoverable': true, + }; + + final entry2 = { + 'id': 'rapid-2', + 'name': 'Rapid Server 2', + 'description': 'Second rapid server', + 'version': '1.0.1', + 'password': 'rapid456', + 'timestamp': DateTime.now().toIso8601String(), + 'ip': '10.0.0.2', + 'author': 'Rapid Author', + 'discoverable': false, + }; + + client1.add(jsonEncode({'type': 'add', 'data': entry1})); + client1.add(jsonEncode({'type': 'add', 'data': entry2})); + client1.add(jsonEncode({'type': 'remove', 'data': 'rapid-1'})); + + await Future.delayed(Duration(milliseconds: 200)); + + await client1.close(); + await client2.close(); + + final client1Messages = await client1MessagesFuture; + final client2Messages = await client2MessagesFuture; + + expect(client1Messages.length, equals(4)); + expect(client2Messages.length, equals(4)); + + final lastAddMessage = jsonDecode(client1Messages[2]); + expect(lastAddMessage['type'], equals('add')); + expect(lastAddMessage['data'][0]['id'], equals('rapid-2')); + + final removeMessage = jsonDecode(client1Messages[3]); + expect(removeMessage['type'], equals('remove')); + expect(removeMessage['data'][0]['id'], equals('rapid-1')); + }); + }); +} \ No newline at end of file