88 lines
2.5 KiB
Dart
88 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,
|
|
}
|
|
|
|
/// Represents an error in a peer service.
|
|
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;
|
|
}
|
|
}
|