import 'dart:io'; import 'dart:math'; import 'package:sync_relay_core/core.dart'; enum MediaGate { ok, gone, unauthorized, forbidden, offline } class ViewerToken { final String token; final String name; final String avatar; bool kicked = false; int bytesServed = 0; DateTime lastSeen; Future Function(String reason)? onKick; ViewerToken({required this.token, required this.name, this.avatar = ''}) : lastSeen = DateTime.now().toUtc(); bool isStale(Duration timeout, [DateTime? now]) => (now ?? DateTime.now().toUtc()).difference(lastSeen) > timeout; void touch([DateTime? now]) => lastSeen = now ?? DateTime.now().toUtc(); } class SessionMedia { final String name; final int size; final double duration; final String? url; final File? file; final int mediaId; const SessionMedia({ required this.name, required this.size, required this.duration, this.url, this.file, this.mediaId = 1, }); } class Session { final String id; final String secret; final Map tokens = {}; SessionMedia? media; final _random = Random.secure(); Session({required this.id, required this.secret}); String issueToken(String name, String avatar) { if (avatar.length > RelayConstants.maxAvatarBytes) { throw const AuthException('avatar too large'); } final trimmed = name.trim(); final cleanName = trimmed.isEmpty ? '?' : trimmed.substring( 0, trimmed.length.clamp(0, RelayConstants.maxNameLength)); final bytes = List.generate(16, (_) => _random.nextInt(256)); final token = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); tokens[token] = ViewerToken(token: token, name: cleanName, avatar: avatar); return token; } MediaGate gate(String? token) { if (token == null || !tokens.containsKey(token)) return MediaGate.unauthorized; if (tokens[token]!.kicked) return MediaGate.forbidden; return MediaGate.ok; } Future 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; } int get totalBytesServed => tokens.values.fold(0, (a, v) => a + v.bytesServed); } class SessionMap { final Map _sessions = {}; Session getOrCreate(String id, String secret) => _sessions.putIfAbsent(id, () => Session(id: id, secret: secret)); Session? get(String id) => _sessions[id]; void drop(String id) => _sessions.remove(id); }