Skip to content

Commit 9b29bec

Browse files
committed
Allow client process to kick user
1 parent f38a614 commit 9b29bec

File tree

9 files changed

+180
-94
lines changed

9 files changed

+180
-94
lines changed

api/lib/src/event/process/client.dart

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,23 @@ bool isValidClientEvent(
8484
_ => true,
8585
};
8686

87-
class ServerResponse {
87+
sealed class ServerResponse {
88+
const ServerResponse();
89+
}
90+
91+
class KickServerResponse extends ServerResponse {
92+
final KickMessage message;
93+
final Set<Channel> kicked;
94+
95+
const KickServerResponse(this.message, {this.kicked = const {}});
96+
}
97+
98+
class UpdateServerResponse extends ServerResponse {
8899
final NetworkerPacket<ServerWorldEvent> main;
89100
final Set<Channel> needsUpdate;
90101

91-
ServerResponse(this.main, [this.needsUpdate = const {}]);
92-
ServerResponse.builder(
102+
UpdateServerResponse(this.main, [this.needsUpdate = const {}]);
103+
UpdateServerResponse.builder(
93104
ServerWorldEvent event, [
94105
Channel channel = kAnyChannel,
95106
this.needsUpdate = const {},
@@ -158,20 +169,20 @@ Future<ServerResponse?> processClientEvent(
158169
if (event == null) {
159170
if (challengeManager != null) {
160171
final challenge = challengeManager.generateNewChallenge(channel);
161-
return ServerResponse.builder(
172+
return UpdateServerResponse.builder(
162173
AuthenticatedRequested(challenge, isRequired: true),
163174
channel,
164175
);
165176
}
166177
await userManager?.addUser(channel);
167-
return ServerResponse.builder(buildInitialize(), channel);
178+
return UpdateServerResponse.builder(buildInitialize(), channel);
168179
}
169180
if (!isValidClientEvent(event, channel, state, assetManager: assetManager)) {
170181
return null;
171182
}
172183
switch (event) {
173184
case HybridWorldEvent():
174-
return ServerResponse.builder(
185+
return UpdateServerResponse.builder(
175186
event,
176187
kAnyChannel,
177188
_hybridNeedsUpdate(event, state),
@@ -180,16 +191,20 @@ Future<ServerResponse?> processClientEvent(
180191
return null;
181192
case ServerWorldEvent():
182193
return allowServerEvents
183-
? ServerResponse.builder(event, kAnyChannel)
194+
? UpdateServerResponse.builder(event, kAnyChannel)
184195
: null;
185196
case TeamJoinRequest(team: final team):
186-
return ServerResponse.builder(TeamJoined(channel, team), kAnyChannel, {
187-
channel,
188-
});
197+
return UpdateServerResponse.builder(
198+
TeamJoined(channel, team),
199+
kAnyChannel,
200+
{channel},
201+
);
189202
case TeamLeaveRequest(team: final team):
190-
return ServerResponse.builder(TeamLeft(channel, team), kAnyChannel, {
191-
channel,
192-
});
203+
return UpdateServerResponse.builder(
204+
TeamLeft(channel, team),
205+
kAnyChannel,
206+
{channel},
207+
);
193208
case CellRollRequest():
194209
final table = state.getTableOrDefault(event.cell.table);
195210
var cell = table.getCell(event.cell.position);
@@ -211,7 +226,7 @@ Future<ServerResponse?> processClientEvent(
211226
} else {
212227
objects = cell.objects.map(roll).toList();
213228
}
214-
return ServerResponse.builder(
229+
return UpdateServerResponse.builder(
215230
ObjectsChanged(event.cell, objects),
216231
kAnyChannel,
217232
);
@@ -221,20 +236,20 @@ Future<ServerResponse?> processClientEvent(
221236
if (cell == null) return null;
222237
final positions = List<int>.generate(cell.objects.length, (i) => i)
223238
..shuffle();
224-
return ServerResponse.builder(
239+
return UpdateServerResponse.builder(
225240
CellShuffled(event.cell, positions),
226241
kAnyChannel,
227242
);
228243
case PacksChangeRequest():
229-
return ServerResponse.builder(
244+
return UpdateServerResponse.builder(
230245
WorldInitialized(
231246
info: state.info.copyWith(
232247
packs: event.packs.where((e) => assetManager.hasPack(e)).toList(),
233248
),
234249
),
235250
);
236251
case MessageRequest():
237-
return ServerResponse.builder(
252+
return UpdateServerResponse.builder(
238253
MessageSent(channel, event.message),
239254
kAnyChannel,
240255
);
@@ -254,7 +269,7 @@ Future<ServerResponse?> processClientEvent(
254269
}
255270
}
256271
}
257-
return ServerResponse.builder(
272+
return UpdateServerResponse.builder(
258273
BoardTilesSpawned(event.table, tiles),
259274
kAnyChannel,
260275
);
@@ -285,7 +300,7 @@ Future<ServerResponse?> processClientEvent(
285300
}
286301
}
287302
}
288-
return ServerResponse.builder(
303+
return UpdateServerResponse.builder(
289304
BoardTilesChanged(event.position.table, newTiles),
290305
kAnyChannel,
291306
);
@@ -323,14 +338,17 @@ Future<ServerResponse?> processClientEvent(
323338
}
324339
}
325340
}
326-
return ServerResponse.builder(
341+
return UpdateServerResponse.builder(
327342
BoardTilesChanged(event.table, newTiles),
328343
kAnyChannel,
329344
);
330345
case DialogCloseRequest():
331-
return ServerResponse.builder(DialogsClosed.single(event.id), channel);
346+
return UpdateServerResponse.builder(
347+
DialogsClosed.single(event.id),
348+
channel,
349+
);
332350
case ImagesRequest():
333-
return ServerResponse.builder(
351+
return UpdateServerResponse.builder(
334352
ImagesUpdated(
335353
Map.fromEntries(
336354
event.ids.map((e) {
@@ -347,7 +365,7 @@ Future<ServerResponse?> processClientEvent(
347365
final mode = location == null
348366
? null
349367
: assetManager.getPack(location.namespace)?.getMode(location.id);
350-
return ServerResponse.builder(
368+
return UpdateServerResponse.builder(
351369
WorldInitialized.fromMode(mode, state),
352370
channel,
353371
);
@@ -357,23 +375,33 @@ Future<ServerResponse?> processClientEvent(
357375
if (challengeManager == null) return null;
358376
final verified = await event.verify(challenge);
359377
if (!verified) {
360-
final newChallenge = challengeManager.generateNewChallenge(channel);
361-
return ServerResponse.builder(
362-
AuthenticatedRequested(newChallenge, isRequired: true),
378+
return UpdateServerResponse.builder(
379+
AuthenticatedRequested(challenge, isRequired: true),
363380
channel,
364381
);
365382
}
366-
final result = await userManager?.addUser(
367-
channel,
368-
generateFingerprint(event.publicKey),
369-
);
370-
if (result == false) {
383+
SetonixUser? user;
384+
try {
385+
user = await userManager?.addUser(
386+
channel,
387+
generateFingerprint(event.publicKey),
388+
);
389+
} catch (e) {
390+
if (e is KickMessage) {
391+
return KickServerResponse(e, kicked: {channel});
392+
} else {
393+
return KickServerResponse(
394+
KickMessage(reason: KickReason.notRegistered),
395+
);
396+
}
397+
}
398+
if (user == null) {
371399
final newChallenge = challengeManager.generateNewChallenge(channel);
372-
return ServerResponse.builder(
400+
return UpdateServerResponse.builder(
373401
AuthenticatedRequested(newChallenge, isRequired: true),
374402
channel,
375403
);
376404
}
377-
return ServerResponse.builder(buildInitialize(), channel);
405+
return UpdateServerResponse.builder(buildInitialize(), channel);
378406
}
379407
}

api/lib/src/models/kick.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,22 @@ import 'package:dart_mappable/dart_mappable.dart';
22

33
part 'kick.mapper.dart';
44

5+
enum KickReason {
6+
kick,
7+
ban,
8+
notWhitelisted,
9+
notRegistered,
10+
challengeFailed,
11+
pleaseLink,
12+
}
13+
514
@MappableClass(hook: KickMessageHook())
615
final class KickMessage with KickMessageMappable {
7-
final String message;
16+
final String? message;
817
final String? link;
18+
final KickReason? reason;
919

10-
const KickMessage({required this.message, this.link});
20+
const KickMessage({this.message, this.link, this.reason});
1121

1222
factory KickMessage.fromString(String message) {
1323
try {

api/lib/src/models/kick.mapper.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,40 @@ class KickMessageMapper extends ClassMapperBase<KickMessage> {
2020
@override
2121
final String id = 'KickMessage';
2222

23-
static String _$message(KickMessage v) => v.message;
23+
static String? _$message(KickMessage v) => v.message;
2424
static const Field<KickMessage, String> _f$message = Field(
2525
'message',
2626
_$message,
27+
opt: true,
2728
);
2829
static String? _$link(KickMessage v) => v.link;
2930
static const Field<KickMessage, String> _f$link = Field(
3031
'link',
3132
_$link,
3233
opt: true,
3334
);
35+
static KickReason? _$reason(KickMessage v) => v.reason;
36+
static const Field<KickMessage, KickReason> _f$reason = Field(
37+
'reason',
38+
_$reason,
39+
opt: true,
40+
);
3441

3542
@override
3643
final MappableFields<KickMessage> fields = const {
3744
#message: _f$message,
3845
#link: _f$link,
46+
#reason: _f$reason,
3947
};
4048

4149
@override
4250
final MappingHook hook = const KickMessageHook();
4351
static KickMessage _instantiate(DecodingData data) {
44-
return KickMessage(message: data.dec(_f$message), link: data.dec(_f$link));
52+
return KickMessage(
53+
message: data.dec(_f$message),
54+
link: data.dec(_f$link),
55+
reason: data.dec(_f$reason),
56+
);
4557
}
4658

4759
@override
@@ -104,7 +116,7 @@ extension KickMessageValueCopy<$R, $Out>
104116

105117
abstract class KickMessageCopyWith<$R, $In extends KickMessage, $Out>
106118
implements ClassCopyWith<$R, $In, $Out> {
107-
$R call({String? message, String? link});
119+
$R call({String? message, String? link, KickReason? reason});
108120
KickMessageCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
109121
}
110122

@@ -117,16 +129,22 @@ class _KickMessageCopyWithImpl<$R, $Out>
117129
late final ClassMapperBase<KickMessage> $mapper =
118130
KickMessageMapper.ensureInitialized();
119131
@override
120-
$R call({String? message, Object? link = $none}) => $apply(
132+
$R call({
133+
Object? message = $none,
134+
Object? link = $none,
135+
Object? reason = $none,
136+
}) => $apply(
121137
FieldCopyWithData({
122-
if (message != null) #message: message,
138+
if (message != $none) #message: message,
123139
if (link != $none) #link: link,
140+
if (reason != $none) #reason: reason,
124141
}),
125142
);
126143
@override
127144
KickMessage $make(CopyWithData data) => KickMessage(
128145
message: data.get(#message, or: $value.message),
129146
link: data.get(#link, or: $value.link),
147+
reason: data.get(#reason, or: $value.reason),
130148
);
131149

132150
@override

api/lib/src/services/user.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ final class UserManager {
7676
return name;
7777
}
7878

79-
Future<bool> addUser(
79+
Future<SetonixUser?> addUser(
8080
Channel channel, [
8181
String? fingerprint,
8282
String? name,
@@ -86,12 +86,12 @@ final class UserManager {
8686
user = await service?.getUser(fingerprint);
8787
if (user != null) name = user.name;
8888
if (whitelistEnabled && user?.onWhitelist != true) {
89-
return false; // User is not on the whitelist
89+
throw KickMessage(reason: KickReason.notWhitelisted);
9090
}
9191
}
9292
name ??= _generateGuestName();
9393
if (containsUserName(name)) {
94-
return false;
94+
return null;
9595
}
9696
if (user == null) {
9797
user = SetonixUser(fingerprint: fingerprint, name: name);
@@ -100,7 +100,7 @@ final class UserManager {
100100
}
101101
}
102102
_users[channel] = user;
103-
return true;
103+
return user;
104104
}
105105

106106
Future<bool> changeName(Channel channel, String newName) async {

app/lib/bloc/world/bloc.dart

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,14 @@ class WorldBloc extends Bloc<PlayableWorldEvent, ClientWorldState> {
174174
userManager: state.multiplayer.state.userManager,
175175
);
176176
if (value == null) return;
177-
state.multiplayer.sendServerPackets(
178-
value.buildPackets(state.world, state.multiplayer.clients),
179-
);
177+
switch (value) {
178+
case UpdateServerResponse():
179+
state.multiplayer.sendServerPackets(
180+
value.buildPackets(state.world, state.multiplayer.clients),
181+
);
182+
case KickServerResponse():
183+
// Handle kick response
184+
}
180185
}
181186

182187
@override
@@ -203,14 +208,13 @@ class WorldBloc extends Bloc<PlayableWorldEvent, ClientWorldState> {
203208
assetManager: state.assetManager,
204209
allowServerEvents: true,
205210
);
206-
if (event != null) {
207-
add(event.main.data);
208-
final updatePacket = event.buildUpdatePackets(state.world, {
209-
kAuthorityChannel,
210-
}).firstOrNull;
211-
if (updatePacket != null) {
212-
add(updatePacket.data);
213-
}
211+
if (event is! UpdateServerResponse) break;
212+
add(event.main.data);
213+
final updatePacket = event.buildUpdatePackets(state.world, {
214+
kAuthorityChannel,
215+
}).firstOrNull;
216+
if (updatePacket != null) {
217+
add(updatePacket.data);
214218
}
215219
}
216220
case ServerWorldEvent e:

app/lib/l10n/app_en.arb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,11 @@
270270
"confirm": "Confirm",
271271
"showIntro": "Show intro",
272272
"highlighted": "Highlighted",
273-
"link": "Link"
273+
"link": "Link",
274+
"kicked": "You have been kicked from the server.",
275+
"banned": "You have been banned from the server.",
276+
"notWhitelisted": "You are not whitelisted on the server.",
277+
"notRegistered": "You are not registered on the server.",
278+
"challengeFailed": "Account challenge failed. Please try again later.",
279+
"pleaseLink": "Please link your account to the server using the provided link."
274280
}

0 commit comments

Comments
 (0)