Guests joining an "Anyone" room saw "Host controls playback" and a
locked room until the host actually started something. Control mode
only travelled inside the host's PlaybackState broadcast, and every
broadcast path requires an active media epoch, so an idle lobby had no
carrier at all: guests sat on the joinAsGuest hostOnly default. The v1
protocol's sessionConfig message covered this; the v3 rewrite lost it.
Carry the mode on the host's join messages instead: the directed join
reply every participant already sends to a new peer, and the host's
reconnect re-announce. The field is optional on the wire ('cm'), so
older clients ignore it and rooms with older hosts degrade to the
previous behavior. Guests apply it only from the relay-derived host
peer ID, never from a join's own spoofable isHost flag.
close #1950
52 lines
2.1 KiB
Dart
52 lines
2.1 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/watch_together/models/sync_message.dart';
|
|
import 'package:plezy/watch_together/models/watch_session.dart';
|
|
|
|
void main() {
|
|
group('join control mode wire format', () {
|
|
test('a host join round-trips its control mode', () {
|
|
final decoded = SyncMessage.fromJson(
|
|
SyncMessage.join(peerId: 'p1', displayName: 'Host', isHost: true, controlMode: ControlMode.anyone).toJson(),
|
|
);
|
|
expect(decoded.type, SyncMessageType.join);
|
|
expect(decoded.controlMode, ControlMode.anyone);
|
|
expect(decoded.version, SyncMessage.protocolVersion);
|
|
});
|
|
|
|
test('hostOnly (index 0) is serialized, not dropped as falsy', () {
|
|
final decoded = SyncMessage.fromJson(
|
|
SyncMessage.join(peerId: 'p1', displayName: 'Host', isHost: true, controlMode: ControlMode.hostOnly).toJson(),
|
|
);
|
|
expect(decoded.controlMode, ControlMode.hostOnly);
|
|
});
|
|
|
|
test('a join without a control mode omits the key and parses to unknown', () {
|
|
final message = SyncMessage.join(peerId: 'p1', displayName: 'Guest', isHost: false);
|
|
final map = jsonDecode(message.toJson()) as Map<String, dynamic>;
|
|
expect(map.containsKey('cm'), isFalse, reason: 'pre-cm clients must see the exact 2.13.0 join shape');
|
|
expect(SyncMessage.fromJson(message.toJson()).controlMode, isNull);
|
|
});
|
|
|
|
test('an out-of-range control mode index from a newer peer parses to unknown', () {
|
|
final map =
|
|
jsonDecode(SyncMessage.join(peerId: 'p1', displayName: 'Host', isHost: true).toJson())
|
|
as Map<String, dynamic>;
|
|
map['cm'] = 99;
|
|
expect(SyncMessage.fromJson(jsonEncode(map)).controlMode, isNull);
|
|
});
|
|
|
|
test('the relay sender stamp preserves the control mode', () {
|
|
final stamped = SyncMessage.join(
|
|
peerId: 'p1',
|
|
displayName: 'Host',
|
|
isHost: true,
|
|
controlMode: ControlMode.hostOnly,
|
|
).copyWith(peerId: 'relay-stamped');
|
|
expect(stamped.controlMode, ControlMode.hostOnly);
|
|
expect(stamped.peerId, 'relay-stamped');
|
|
});
|
|
});
|
|
}
|