Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/harden-rpc-tester
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patch type="fixed" "Harden the example RPC tester"
66 changes: 46 additions & 20 deletions example/lib/widgets/rpc_test_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ class RpcHandlerEntry {
}

class RpcTestController extends ChangeNotifier {
// Oldest invocations are dropped past this point so a chatty peer cannot
// grow the log without bound.
static const _maxInvocationsPerHandler = 200;

final List<RpcHandlerEntry> _handlers = [];
Room? _room;
bool _disposed = false;

List<RpcHandlerEntry> get handlers => List.unmodifiable(_handlers);

Expand Down Expand Up @@ -60,21 +65,33 @@ class RpcTestController extends ChangeNotifier {
}

final entry = RpcHandlerEntry(topic: topic, staticResponse: staticResponse);
try {
room.registerRpcMethod(topic, (data) async {
final bytes = utf8.encode(data.payload).length;
entry.invocations.insert(
0,
RpcInvocationRecord(
timestamp: DateTime.now(),
byteLength: bytes,
payload: data.payload,
callerIdentity: data.callerIdentity,
),
);
if (entry.invocations.length > _maxInvocationsPerHandler) {
entry.invocations.removeLast();
}
if (!_disposed) {
notifyListeners();
}
return entry.staticResponse;
});
} on Exception {
// The room throws when another component already registered this
// method. Adding a card for it would let the tester unregister a
// handler it does not own.
return false;
}
_handlers.add(entry);
room.registerRpcMethod(topic, (data) async {
final bytes = utf8.encode(data.payload).length;
entry.invocations.insert(
0,
RpcInvocationRecord(
timestamp: DateTime.now(),
byteLength: bytes,
payload: data.payload,
callerIdentity: data.callerIdentity,
),
);
notifyListeners();
return entry.staticResponse;
});
notifyListeners();
return true;
}
Expand All @@ -97,15 +114,14 @@ class RpcTestController extends ChangeNotifier {
// is needed. Skip notifyListeners to avoid rebuild loops with the
// editing TextField that drives this method.
void updateStaticResponse(String topic, String response) {
final entry = _handlers.firstWhere(
(h) => h.topic == topic,
orElse: () => throw StateError('topic $topic not registered'),
);
final entry = _handlers.firstWhere((h) => h.topic == topic);
entry.staticResponse = response;
}

@override
void dispose() {
// The flag keeps in flight RPC handlers from notifying after disposal.
_disposed = true;
_unregisterAll();
_room = null;
super.dispose();
Expand Down Expand Up @@ -193,8 +209,13 @@ class _RpcTestSheetState extends State<RpcTestSheet> {
super.dispose();
}

// The selected identity is only usable while that participant is still in
// the room.
String? get _validSelectedIdentity =>
widget.room.remoteParticipants.values.any((p) => p.identity == _selectedIdentity) ? _selectedIdentity : null;

Future<void> _send() async {
final identity = _selectedIdentity;
final identity = _validSelectedIdentity;
final method = _methodCtl.text.trim();
final local = widget.room.localParticipant;
if (identity == null || method.isEmpty || local == null) {
Expand Down Expand Up @@ -321,7 +342,12 @@ class _RpcTestSheetState extends State<RpcTestSheet> {
final remotes = widget.room.remoteParticipants.values.toList();
final identities = remotes.map((p) => p.identity).toList();
final currentValue = identities.contains(_selectedIdentity) ? _selectedIdentity : null;
// The form field only reads initialValue when its state is
// created, so key it by the participant list. Otherwise a
// selection can outlive its participant and trip the framework
// assert that the value must be among the items.
return DropdownButtonFormField<String>(
key: ValueKey(identities.join(',')),
initialValue: currentValue,
decoration: const InputDecoration(
labelText: 'Destination',
Expand Down Expand Up @@ -401,7 +427,7 @@ class _RpcTestSheetState extends State<RpcTestSheet> {

bool _canSend() =>
!_isSending &&
_selectedIdentity != null &&
_validSelectedIdentity != null &&
_methodCtl.text.trim().isNotEmpty &&
widget.room.localParticipant != null;

Expand Down
Loading