Files
plezy/lib/services/base_peer_service.dart

87 lines
2.5 KiB
Dart

import 'dart:async';
/// Error types that can occur in peer services.
///
/// This is a superset covering both Watch Together relay errors and
/// Companion Remote direct-connection errors.
enum PeerErrorType {
connectionFailed,
peerDisconnected,
dataChannelError,
serverError,
timeout,
invalidSession,
authFailed,
networkError,
unknown,
}
class PeerError {
final PeerErrorType type;
final String message;
final dynamic originalError;
/// Server-provided error code (e.g. 'room_not_found', 'room_full').
final String? serverCode;
const PeerError({required this.type, required this.message, this.originalError, this.serverCode});
@override
String toString() => 'PeerError($type): $message';
}
/// Mixin that provides WebSocket keepalive ping/pong timer management.
///
/// Subclasses must implement [sendPing] to send the actual ping message
/// over their specific transport. The mixin manages periodic pings and
/// an optional pong timeout that closes the connection if no data arrives.
mixin KeepaliveMixin {
Timer? _pingTimer;
Timer? _pongTimer;
DateTime? _lastPongReset;
/// How often to send a keepalive ping.
Duration get pingInterval;
/// How long to wait for any incoming data before considering the
/// connection dead. Set to [Duration.zero] to disable pong timeout.
Duration get pongTimeout;
/// Send a keepalive ping over the transport.
void sendPing();
/// Called when the pong timeout fires (no data received within [pongTimeout]).
/// Override to close the underlying socket/channel.
void onPongTimeout();
/// Start the periodic ping timer and arm the pong timeout.
void startKeepalive() {
_pingTimer?.cancel();
_pingTimer = Timer.periodic(pingInterval, (_) => sendPing());
_lastPongReset = null; // Force first reset
resetPongTimer();
}
/// Reset the pong timeout (call on every incoming message).
/// Coalesced: skips if last reset was <5s ago (timeout is 30s, still safe).
void resetPongTimer() {
if (pongTimeout == Duration.zero) return;
final now = DateTime.now();
if (_lastPongReset != null && now.difference(_lastPongReset!) < const Duration(seconds: 5)) {
return;
}
_lastPongReset = now;
_pongTimer?.cancel();
_pongTimer = Timer(pongTimeout, onPongTimeout);
}
/// Stop all keepalive timers.
void stopKeepalive() {
_pingTimer?.cancel();
_pingTimer = null;
_pongTimer?.cancel();
_pongTimer = null;
_lastPongReset = null;
}
}