Skip to content

Commit 1f58073

Browse files
committed
refactor: replace on String catch with typed RTC exceptions via RtcJsepErrorParser
1 parent 261eb97 commit 1f58073

5 files changed

Lines changed: 70 additions & 41 deletions

File tree

lib/features/call/utils/call_error_reporter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:webtrit_signaling/webtrit_signaling.dart';
77
import 'package:webtrit_phone/app/notifications/models/notification.dart';
88

99
import '../models/notification.dart';
10+
import 'pc_exceptions.dart';
1011
import 'user_media_builder.dart';
1112

1213
final _logger = Logger('CallBloc:SignalingErrorReporter');
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
abstract class PCWrongSignalingState implements Exception {
2+
final String message;
3+
4+
const PCWrongSignalingState(this.message);
5+
}
6+
7+
class RtcSetRemoteDescriptionWhileHaveLocalOffer extends PCWrongSignalingState {
8+
const RtcSetRemoteDescriptionWhileHaveLocalOffer(super.message);
9+
}
10+
11+
class RtcCreateAnswerWhileWrongState extends PCWrongSignalingState {
12+
const RtcCreateAnswerWhileWrongState(super.message);
13+
}
14+
15+
class SDPConfigurationError implements Exception {
16+
final String message;
17+
18+
const SDPConfigurationError(this.message);
19+
20+
@override
21+
String toString() => 'SDPConfigurationError: $message';
22+
}
23+
24+
class RtcJsepErrorParser {
25+
static Exception parse(Object error) {
26+
final msg = error.toString();
27+
if (msg.contains('have-local-offer') || msg.contains('setRemoteDescription')) {
28+
return RtcSetRemoteDescriptionWhileHaveLocalOffer(msg);
29+
}
30+
if (msg.contains('createAnswer') || msg.contains('wrong state')) {
31+
return RtcCreateAnswerWhileWrongState(msg);
32+
}
33+
return SDPConfigurationError(msg);
34+
}
35+
}

lib/features/call/utils/renegotiation_handler.dart

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
44
import 'package:logging/logging.dart';
55
import 'package:webtrit_signaling/webtrit_signaling.dart';
66
import 'call_error_reporter.dart';
7+
import 'pc_exceptions.dart';
78
import 'sdp_munger.dart';
89

910
final _logger = Logger('RenegotiationHandler');
@@ -44,7 +45,7 @@ typedef RenegotiationExecutor = Future<void> Function(String callId, int? lineId
4445
/// calling `setLocalDescription`. The `await createOffer()` yields control
4546
/// to the event loop; a concurrent native callback may update the Dart-side
4647
/// cached [RTCPeerConnection.signalingState] in that gap. If the check misses
47-
/// a concurrent state change (stale cache), the [on String catch] below is the
48+
/// a concurrent state change (stale cache), the [RtcJsepErrorParser] below is the
4849
/// authoritative fallback.
4950
///
5051
/// ## Concurrency guard
@@ -90,52 +91,52 @@ class RenegotiationHandler {
9091
_isHandling = true;
9192
_pendingRetry = false;
9293
try {
93-
final stateBeforeOffer = peerConnection.signalingState;
94-
_logger.fine(() => 'onRenegotiationNeeded signalingState: $stateBeforeOffer');
95-
if (stateBeforeOffer != RTCSignalingState.RTCSignalingStateStable) {
96-
_logger.fine(() => 'onRenegotiationNeeded skipped: not in stable state ($stateBeforeOffer)');
97-
return;
98-
}
94+
try {
95+
final stateBeforeOffer = peerConnection.signalingState;
96+
_logger.fine(() => 'onRenegotiationNeeded signalingState: $stateBeforeOffer');
97+
if (stateBeforeOffer != RTCSignalingState.RTCSignalingStateStable) {
98+
_logger.fine(() => 'onRenegotiationNeeded skipped: not in stable state ($stateBeforeOffer)');
99+
return;
100+
}
99101

100-
final localDescription = await peerConnection.createOffer({});
101-
sdpMunger?.apply(localDescription);
102-
_logger.info(() => 'onRenegotiationNeeded offer SDP (callId=$callId):\n${localDescription.sdp}');
102+
final localDescription = await peerConnection.createOffer({});
103+
sdpMunger?.apply(localDescription);
104+
_logger.info(() => 'onRenegotiationNeeded offer SDP (callId=$callId):\n${localDescription.sdp}');
103105

104-
final stateAfterOffer = peerConnection.signalingState;
105-
if (stateAfterOffer != RTCSignalingState.RTCSignalingStateStable) {
106-
_logger.fine(
107-
() =>
108-
'onRenegotiationNeeded: state changed to $stateAfterOffer after createOffer, skipping setLocalDescription',
109-
);
110-
return;
111-
}
106+
final stateAfterOffer = peerConnection.signalingState;
107+
if (stateAfterOffer != RTCSignalingState.RTCSignalingStateStable) {
108+
_logger.fine(
109+
() =>
110+
'onRenegotiationNeeded: state changed to $stateAfterOffer after createOffer, skipping setLocalDescription',
111+
);
112+
return;
113+
}
112114

113-
// According to RFC 8829 5.6 (https://datatracker.ietf.org/doc/html/rfc8829#section-5.6),
114-
// localDescription should be set before sending the offer to transition into have-local-offer state.
115-
await peerConnection.setLocalDescription(localDescription);
115+
// According to RFC 8829 5.6 (https://datatracker.ietf.org/doc/html/rfc8829#section-5.6),
116+
// localDescription should be set before sending the offer to transition into have-local-offer state.
117+
await peerConnection.setLocalDescription(localDescription);
116118

117-
await execute(callId, lineId, localDescription);
119+
await execute(callId, lineId, localDescription);
120+
} on String catch (e) {
121+
throw RtcJsepErrorParser.parse(e);
122+
}
118123
} on WebtritSignalingErrorException catch (e) {
119124
_logger.warning(
120125
() => 'onRenegotiationNeeded: UpdateRequest rejected by server (callId=$callId, lineId=$lineId): $e',
121126
);
122127
callErrorReporter.handle(e, null, 'RenegotiationHandler.handle error (callId=$callId, lineId=$lineId)');
123-
} on String catch (e) {
128+
} on PCWrongSignalingState catch (e) {
124129
// flutter_webrtc surfaces native errors as plain strings. A "wrong state" failure
125130
// on setLocalDescription means a concurrent setRemoteDescription (e.g. from an
126131
// incoming updating_call) moved the PC out of stable between the TOCTOU guard and
127132
// the setLocalDescription call. This is a transient race — libwebrtc keeps the
128133
// [[NegotiationNeeded]] flag set and will re-fire onRenegotiationNeeded once the
129134
// PC returns to stable. No user notification is needed.
130-
if (e.contains('wrong state') || e.contains('have-remote-offer') || e.contains('have-local-offer')) {
131-
_logger.warning(
132-
() =>
133-
'onRenegotiationNeeded: setLocalDescription failed in wrong state ($e) '
134-
'— libwebrtc will re-fire onRenegotiationNeeded when stable',
135-
);
136-
} else {
137-
callErrorReporter.handle(e, null, 'RenegotiationHandler.handle error (callId=$callId, lineId=$lineId)');
138-
}
135+
_logger.warning(
136+
() =>
137+
'onRenegotiationNeeded: setLocalDescription failed in wrong state (${e.message}) '
138+
'— libwebrtc will re-fire onRenegotiationNeeded when stable',
139+
);
139140
} catch (e, s) {
140141
callErrorReporter.handle(e, s, 'RenegotiationHandler.handle error (callId=$callId, lineId=$lineId)');
141142
} finally {

lib/features/call/utils/user_media_builder.dart

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,3 @@ class UserMediaError implements Exception {
6767
@override
6868
String toString() => 'UserMediaError: $message';
6969
}
70-
71-
class SDPConfigurationError implements Exception {
72-
final String message;
73-
74-
SDPConfigurationError(this.message);
75-
76-
@override
77-
String toString() => 'SDPConfigurationError: $message';
78-
}

lib/features/call/utils/utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export 'audio_constraints_builder.dart';
2+
export 'pc_exceptions.dart';
23
export 'call_error_reporter.dart';
34
export 'compact_auto_reset_controller.dart';
45
export 'contact_name_resolver.dart';

0 commit comments

Comments
 (0)