From 7e7866de61741a822f582021906163bee7f8abbb Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Fri, 6 Sep 2024 18:26:06 +0200 Subject: [PATCH] Fix reconnect, add say and kick to server, add cors --- app/lib/api/open.dart | 2 +- app/lib/bloc/multiplayer.dart | 4 +- app/lib/helpers/asset.dart | 3 +- app/lib/main.dart | 5 ++- app/lib/pages/game/page.dart | 4 +- app/lib/pages/home/connect.dart | 35 +++++++++++----- app/lib/pages/home/page.dart | 2 +- app/pubspec.lock | 4 +- app/pubspec.yaml | 2 +- server/lib/main.dart | 72 +++++++++++++++++++++++---------- server/lib/programs/kick.dart | 32 +++++++++++++++ server/lib/programs/say.dart | 17 ++++++++ 12 files changed, 139 insertions(+), 43 deletions(-) create mode 100644 server/lib/programs/kick.dart create mode 100644 server/lib/programs/say.dart diff --git a/app/lib/api/open.dart b/app/lib/api/open.dart index 6ebeb81..be38273 100644 --- a/app/lib/api/open.dart +++ b/app/lib/api/open.dart @@ -22,7 +22,7 @@ Future openHelp(List pageLocation, [String? fragment]) { scheme: 'https', host: 'quokka.linwood.dev', fragment: fragment, - pathSegments: ['docs', 'v0', ...pageLocation]), + pathSegments: ['docs', 'v1', ...pageLocation]), ); } diff --git a/app/lib/bloc/multiplayer.dart b/app/lib/bloc/multiplayer.dart index ccfb9d0..4da67e0 100644 --- a/app/lib/bloc/multiplayer.dart +++ b/app/lib/bloc/multiplayer.dart @@ -139,12 +139,12 @@ class MultiplayerCubit extends Cubit { } } - Future connect(String address) async { + Future connect(String address, {bool secure = true}) async { try { emit(MultiplayerConnectingState()); final add = address.split(':'); final client = NetworkerSocketClient(Uri( - scheme: 'ws', + scheme: secure ? 'wss' : 'ws', host: add[0], port: add.length <= 1 ? kDefaultPort : int.parse(add[1]), )); diff --git a/app/lib/helpers/asset.dart b/app/lib/helpers/asset.dart index c5041fa..7cada66 100644 --- a/app/lib/helpers/asset.dart +++ b/app/lib/helpers/asset.dart @@ -90,7 +90,8 @@ class GameAssetManager extends AssetManager { Future loadPacks() async { final files = await fileSystem.getPacks(); - unloadPacks(_loadedPacks.keys.where((e) => !files.any((f) => f.path == e))); + unloadPacks(_loadedPacks.keys + .where((e) => !files.any((f) => f.pathWithoutLeadingSlash == e))); for (final file in files) { try { final key = file.pathWithoutLeadingSlash; diff --git a/app/lib/main.dart b/app/lib/main.dart index 18ab0f0..d995424 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -125,6 +125,9 @@ class QuokkaApp extends StatelessWidget { path: 'connect', builder: (context, state) => GamePage( address: state.uri.queryParameters['address'], + secure: + bool.tryParse(state.uri.queryParameters['secure'] ?? '') ?? + true, ), ), GoRoute( @@ -157,4 +160,4 @@ const isNightly = flavor == 'nightly' || flavor == 'dev' || flavor == 'development'; const shortApplicationName = isNightly ? 'Quokka Nightly' : 'Quokka'; const applicationName = 'Linwood $shortApplicationName'; -const applicationMinorVersion = '1.0'; +const applicationMinorVersion = '0.1'; diff --git a/app/lib/pages/game/page.dart b/app/lib/pages/game/page.dart index dfc8890..0a2b78a 100644 --- a/app/lib/pages/game/page.dart +++ b/app/lib/pages/game/page.dart @@ -20,6 +20,7 @@ import 'package:quokka_api/quokka_api.dart'; class GamePage extends StatefulWidget { final String? name; final String? address; + final bool secure; final QuokkaData? data; const GamePage({ @@ -27,6 +28,7 @@ class GamePage extends StatefulWidget { this.name, this.data, this.address, + this.secure = true, }); @override @@ -61,7 +63,7 @@ class _GamePageState extends State { ); await world.assetManager.loadPacks(); if (address != null) { - cubit.connect(address); + cubit.connect(address, secure: widget.secure); } return (cubit, world); } diff --git a/app/lib/pages/home/connect.dart b/app/lib/pages/home/connect.dart index 13cc774..ec527b5 100644 --- a/app/lib/pages/home/connect.dart +++ b/app/lib/pages/home/connect.dart @@ -8,12 +8,20 @@ import 'package:quokka/bloc/settings.dart'; import 'package:quokka/widgets/search.dart'; class AddConnectDialog extends StatelessWidget { - final TextEditingController _controller = TextEditingController(); - - AddConnectDialog({super.key}); + const AddConnectDialog({super.key}); @override Widget build(BuildContext context) { + String address = ''; + bool secure = true; + void connect() { + Navigator.of(context).pop(); + GoRouter.of(context).goNamed('connect', queryParameters: { + 'address': address, + 'secure': secure.toString(), + }); + } + return ResponsiveAlertDialog( title: Text(AppLocalizations.of(context).connect), leading: IconButton.outlined( @@ -26,23 +34,28 @@ class AddConnectDialog extends StatelessWidget { children: [ Text(AppLocalizations.of(context).connectNote), const SizedBox(height: 16), - TextField( - controller: _controller, + TextFormField( + initialValue: address, + onChanged: (value) => address = value, decoration: InputDecoration( labelText: AppLocalizations.of(context).address, filled: true, ), + onFieldSubmitted: (_) => connect(), + ), + const SizedBox(height: 8), + StatefulBuilder( + builder: (context, setState) => SwitchListTile( + title: Text(AppLocalizations.of(context).secure), + value: secure, + onChanged: (value) => setState(() => secure = value), + ), ), ], ), actions: [ FilledButton.icon( - onPressed: () { - Navigator.of(context).pop(); - GoRouter.of(context).goNamed('connect', queryParameters: { - 'address': _controller.text, - }); - }, + onPressed: connect, label: Text(AppLocalizations.of(context).connect), icon: const Icon(PhosphorIconsLight.link), ), diff --git a/app/lib/pages/home/page.dart b/app/lib/pages/home/page.dart index 2f29d18..3d02e68 100644 --- a/app/lib/pages/home/page.dart +++ b/app/lib/pages/home/page.dart @@ -32,7 +32,7 @@ class HomePage extends StatelessWidget { AppLocalizations.of(context).connect, PhosphorIconsLight.plugsConnected, () => showDialog( - context: context, builder: (context) => AddConnectDialog()), + context: context, builder: (context) => const AddConnectDialog()), null, ), ( diff --git a/app/pubspec.lock b/app/pubspec.lock index 912d0b2..28b9c4b 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -749,8 +749,8 @@ packages: dependency: "direct main" description: path: "packages/networker/networker_socket" - ref: "00671cce35e995f203806b55bc6d6881fca0e107" - resolved-ref: "00671cce35e995f203806b55bc6d6881fca0e107" + ref: c9c0f1570f2e6ce11faa91bd6f70e322a768fb80 + resolved-ref: c9c0f1570f2e6ce11faa91bd6f70e322a768fb80 url: "https://github.com/LinwoodDev/dart_pkgs" source: git version: "1.0.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 5bfa02a..d07789f 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -63,7 +63,7 @@ dependencies: networker_socket: git: url: https://github.com/LinwoodDev/dart_pkgs - ref: 00671cce35e995f203806b55bc6d6881fca0e107 + ref: c9c0f1570f2e6ce11faa91bd6f70e322a768fb80 path: packages/networker/networker_socket # System Information dynamic_color: ^1.7.0 diff --git a/server/lib/main.dart b/server/lib/main.dart index bf58ce2..b6b3ce1 100644 --- a/server/lib/main.dart +++ b/server/lib/main.dart @@ -10,6 +10,7 @@ import 'package:quokka_server/asset.dart'; import 'package:quokka_server/programs/packs.dart'; import 'package:quokka_server/programs/players.dart'; import 'package:quokka_server/programs/save.dart'; +import 'package:quokka_server/programs/say.dart'; import 'package:quokka_server/programs/stop.dart'; final class QuokkaServer extends Bloc { @@ -21,17 +22,13 @@ final class QuokkaServer extends Bloc { NetworkerSocketServer? _server; NetworkerPipe? _pipe; - QuokkaServer._(this.worldFile, QuokkaData data) - : assetManager = ServerAssetManager(), - consoler = Consoler( - defaultProgramConfig: DefaultProgramConfiguration( - description: "Quokka server", - ), - ), - super(WorldState( + QuokkaServer._( + this.worldFile, this.consoler, QuokkaData data, this.assetManager) + : super(WorldState( data: data, table: data.getTableOrDefault(), metadata: data.getMetadataOrDefault(), + info: data.getInfoOrDefault(), )) { on((event, emit) { final newState = @@ -42,24 +39,35 @@ final class QuokkaServer extends Bloc { }); } - QuokkaServer.empty() : this._(null, QuokkaData.empty()); - - static Future load({String? worldFile}) async { + static Future load({ + String? worldFile, + bool disableLoading = false, + }) async { + final assetManager = ServerAssetManager(); + final consoler = Consoler( + defaultProgramConfig: DefaultProgramConfiguration( + description: "Quokka server", + ), + ); + await _runStaticLogZone( + consoler, () => assetManager.init(console: consoler)); worldFile ??= defaultWorldFile; final file = File(worldFile); QuokkaData? data; - if (await file.exists()) { + if (!disableLoading && await file.exists()) { final bytes = await file.readAsBytes(); data = QuokkaData.fromData(bytes); } - data ??= QuokkaData.empty(); - return QuokkaServer._(worldFile, data); + data ??= QuokkaData.empty().setInfo(GameInfo( + packs: assetManager.packs.map((e) => e.key).toList(), + )); + return QuokkaServer._(worldFile, consoler, data, assetManager); } void log(Object? message, {LogLevel? level}) => consoler.print(message, level: level); - static String get defaultWorldFile => 'world.qka'; + static final String defaultWorldFile = 'world.qka'; Map get players => Map.fromEntries((_server?.clientConnections ?? {}) @@ -69,17 +77,15 @@ final class QuokkaServer extends Bloc { {int port = kDefaultPort, bool verbose = false, bool autosave = false}) async { - await _runLogZone(() async { - await assetManager.init(console: consoler, verbose: verbose); - }); if (verbose) { consoler.minLogLevel = LogLevel.verbose; } log("Starting server on port $port", level: LogLevel.info); log('Verbose logging activated', level: LogLevel.verbose); _temp = autosave; - final server = - _server = NetworkerSocketServer(InternetAddress.anyIPv4, port); + final server = _server = NetworkerSocketServer( + InternetAddress.anyIPv4, port, + filterConnections: _filterConnections); final transformer = _pipe = NetworkerPipeTransformer( WorldEventMapper.fromJson, (e) => e.toJson()); transformer.read.listen(_onClientEvent); @@ -93,15 +99,17 @@ final class QuokkaServer extends Bloc { consoler.registerProgram('save', SaveProgram(this)); consoler.registerProgram('packs', PacksProgram(this)); consoler.registerProgram('players', PlayersProgram(this)); + consoler.registerProgram('say', SayProgram(this)); consoler.registerProgram(null, UnknownProgram()); } - R _runLogZone(R Function() body) => + static R _runStaticLogZone(Consoler consoler, R Function() body) => runZoned(body, zoneSpecification: ZoneSpecification( print: (self, parent, zone, message) { - log(message); + consoler.print(message); }, )); + R _runLogZone(R Function() body) => _runStaticLogZone(consoler, body); Future run() async { _runLogZone(() { @@ -154,4 +162,24 @@ final class QuokkaServer extends Bloc { _server?.close(); consoler.dispose(); } + + void process(WorldEvent event) { + _onClientEvent(NetworkerPacket(event, kAuthorityChannel)); + } + + bool kick(int id) { + final info = _server?.getConnectionInfo(id); + if (info == null) return false; + info.close(); + return true; + } + + bool _filterConnections(HttpRequest request) { + request.response.headers.add("Access-Control-Allow-Origin", "*"); + request.response.headers + .add("Access-Control-Allow-Methods", "POST,GET,DELETE,PUT,OPTIONS"); + + request.response.statusCode = HttpStatus.ok; + return true; + } } diff --git a/server/lib/programs/kick.dart b/server/lib/programs/kick.dart new file mode 100644 index 0000000..c3fca67 --- /dev/null +++ b/server/lib/programs/kick.dart @@ -0,0 +1,32 @@ +import 'package:consoler/consoler.dart'; +import 'package:quokka_server/main.dart'; + +class PlayersProgram extends ConsoleProgram { + final QuokkaServer server; + + PlayersProgram(this.server); + + @override + String getDescription() => "Kick a player"; + + @override + String getUsage() => 'kick '; + + @override + void run(String label, List args) { + if (args.length != 1) { + server.log("Wrong usage, use ${getUsage()}", level: LogLevel.error); + } + final arg = int.tryParse(args[0]); + if (arg == null) { + server.log("ID should be a number", level: LogLevel.error); + return; + } + final result = server.kick(arg); + if (result) { + server.log("$arg successfully kicked.", level: LogLevel.info); + } else { + server.log("$arg could not be kicked.", level: LogLevel.error); + } + } +} diff --git a/server/lib/programs/say.dart b/server/lib/programs/say.dart new file mode 100644 index 0000000..0251c74 --- /dev/null +++ b/server/lib/programs/say.dart @@ -0,0 +1,17 @@ +import 'package:consoler/consoler.dart'; +import 'package:quokka_api/quokka_api.dart'; +import 'package:quokka_server/main.dart'; + +class SayProgram extends ConsoleProgram { + final QuokkaServer server; + + SayProgram(this.server); + @override + String getDescription() => "Send a message in the message"; + + @override + Future run(String label, List args) async { + final message = args.join(' '); + server.process(MessageRequest(message)); + } +}