Protocol 11: sit out replaces kicking; nudge
Anyone can sit anyone out (host included); only you bring yourself back. Out members stay in chat and the party, never block or count, and load no media; a card with the live position and Join back (Space) replaces the video. The mini player's x sits you out; optionally idle does too. Nudge: the server forwards it to the target only (3/s, 20/min per sender); the target hears the nudge sound, gets a toast and a taskbar attention request. Kick, its messages, errors and dialog are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dbfd351ee4
commit
8b81da4069
46 files changed
+855
-599
No files matched your search
@@ -3,21 +3,31 @@ import 'dart:math';
|
||||
|
||||
import 'package:sync_relay_core/core.dart';
|
||||
|
||||
enum MediaGate { ok, gone, unauthorized, forbidden, offline }
|
||||
enum MediaGate { ok, gone, unauthorized, offline }
|
||||
|
||||
class ViewerToken {
|
||||
final String token;
|
||||
/// Cleaned ([cleanMemberName]); follows `profile` renames.
|
||||
String name;
|
||||
bool kicked = false;
|
||||
/// Sent the server's host key in hello: the hosting app's own client.
|
||||
bool isHost = false;
|
||||
int bytesServed = 0;
|
||||
DateTime lastSeen;
|
||||
Future<void> Function(String reason)? onKick;
|
||||
/// When this member's recent nudges went out, for the rate cap.
|
||||
final List<DateTime> nudges = [];
|
||||
ViewerToken({required this.token, required this.name})
|
||||
: lastSeen = DateTime.now().toUtc();
|
||||
|
||||
/// Takes one nudge at [now] if under the caps
|
||||
/// ([RelayConstants.nudgesPerSec] / [RelayConstants.nudgesPerMin]).
|
||||
bool takeNudge(DateTime now) {
|
||||
nudges.removeWhere((t) => now.difference(t) >= const Duration(minutes: 1));
|
||||
final lastSec = nudges.where((t) => now.difference(t) < const Duration(seconds: 1)).length;
|
||||
if (nudges.length >= RelayConstants.nudgesPerMin || lastSec >= RelayConstants.nudgesPerSec) return false;
|
||||
nudges.add(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isStale(Duration timeout, [DateTime? now]) =>
|
||||
(now ?? DateTime.now().toUtc()).difference(lastSeen) > timeout;
|
||||
|
||||
@@ -66,17 +76,9 @@ class Session {
|
||||
|
||||
MediaGate gate(String? token) {
|
||||
if (token == null || !tokens.containsKey(token)) return MediaGate.unauthorized;
|
||||
if (tokens[token]!.kicked) return MediaGate.forbidden;
|
||||
return MediaGate.ok;
|
||||
}
|
||||
|
||||
Future<void> kick(String token, {String reason = ''}) async {
|
||||
final viewer = tokens[token];
|
||||
if (viewer == null) throw const AuthException('unknown token');
|
||||
viewer.kicked = true;
|
||||
await viewer.onKick?.call(reason);
|
||||
}
|
||||
|
||||
void addBytes(String? token, int n) {
|
||||
if (token != null) tokens[token]?.bytesServed += n;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user