Skip to content

Commit

Permalink
Fix reconnect, add say and kick to server, add cors
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeDoctorDE committed Sep 6, 2024
1 parent fbda1b5 commit 7e7866d
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 43 deletions.
2 changes: 1 addition & 1 deletion app/lib/api/open.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Future<bool> openHelp(List<String> pageLocation, [String? fragment]) {
scheme: 'https',
host: 'quokka.linwood.dev',
fragment: fragment,
pathSegments: ['docs', 'v0', ...pageLocation]),
pathSegments: ['docs', 'v1', ...pageLocation]),
);
}

Expand Down
4 changes: 2 additions & 2 deletions app/lib/bloc/multiplayer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ class MultiplayerCubit extends Cubit<MultiplayerState> {
}
}

Future<void> connect(String address) async {
Future<void> 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]),
));
Expand Down
3 changes: 2 additions & 1 deletion app/lib/helpers/asset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class GameAssetManager extends AssetManager {

Future<void> 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;
Expand Down
5 changes: 4 additions & 1 deletion app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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';
4 changes: 3 additions & 1 deletion app/lib/pages/game/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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({
super.key,
this.name,
this.data,
this.address,
this.secure = true,
});

@override
Expand Down Expand Up @@ -61,7 +63,7 @@ class _GamePageState extends State<GamePage> {
);
await world.assetManager.loadPacks();
if (address != null) {
cubit.connect(address);
cubit.connect(address, secure: widget.secure);
}
return (cubit, world);
}
Expand Down
35 changes: 24 additions & 11 deletions app/lib/pages/home/connect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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),
),
Expand Down
2 changes: 1 addition & 1 deletion app/lib/pages/home/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
(
Expand Down
4 changes: 2 additions & 2 deletions app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 50 additions & 22 deletions server/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServerWorldEvent, WorldState> {
Expand All @@ -21,17 +22,13 @@ final class QuokkaServer extends Bloc<ServerWorldEvent, WorldState> {
NetworkerSocketServer? _server;
NetworkerPipe<dynamic, WorldEvent>? _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<ServerWorldEvent>((event, emit) {
final newState =
Expand All @@ -42,24 +39,35 @@ final class QuokkaServer extends Bloc<ServerWorldEvent, WorldState> {
});
}

QuokkaServer.empty() : this._(null, QuokkaData.empty());

static Future<QuokkaServer> load({String? worldFile}) async {
static Future<QuokkaServer> 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<int, ConnectionInfo> get players =>
Map.fromEntries((_server?.clientConnections ?? {})
Expand All @@ -69,17 +77,15 @@ final class QuokkaServer extends Bloc<ServerWorldEvent, WorldState> {
{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<String, WorldEvent>(
WorldEventMapper.fromJson, (e) => e.toJson());
transformer.read.listen(_onClientEvent);
Expand All @@ -93,15 +99,17 @@ final class QuokkaServer extends Bloc<ServerWorldEvent, WorldState> {
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>(R Function() body) =>
static R _runStaticLogZone<R>(Consoler consoler, R Function() body) =>
runZoned(body, zoneSpecification: ZoneSpecification(
print: (self, parent, zone, message) {
log(message);
consoler.print(message);
},
));
R _runLogZone<R>(R Function() body) => _runStaticLogZone(consoler, body);

Future<void> run() async {
_runLogZone(() {
Expand Down Expand Up @@ -154,4 +162,24 @@ final class QuokkaServer extends Bloc<ServerWorldEvent, WorldState> {
_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;
}
}
32 changes: 32 additions & 0 deletions server/lib/programs/kick.dart
Original file line number Diff line number Diff line change
@@ -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 <ID>';

@override
void run(String label, List<String> 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);
}
}
}
17 changes: 17 additions & 0 deletions server/lib/programs/say.dart
Original file line number Diff line number Diff line change
@@ -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<void> run(String label, List<String> args) async {
final message = args.join(' ');
server.process(MessageRequest(message));
}
}

0 comments on commit 7e7866d

Please sign in to comment.