Server on an isolate, dedicated server binary, room -> server (proto 5)

- serve(Map config, events) is the one server entrypoint: the app
  hosts it via Isolate.spawn (crash surfaced on the Host card, logs
  forwarded as [server]); bin/server.dart is the dedicated server,
  compiled into the bundle as watch-together-server. --headless gone.
- Host identity via a per-hosting hostKey in hello, replacing the
  loopback heuristic.
- Rename room -> server across code, files and wire (t:"state",
  /health name); protocol bumped to 5.
- CI compiles the server binary; isolate smoke tests in server and
  desktop suites. Docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-07 00:55:14 +03:00
1 parent d8a0729029
commit 81cac1214d
48 files changed
+1073 -423

No files matched your search

+13 -11
View File
@@ -9,13 +9,13 @@ enum SyncStatus { idle, connecting, open, kicked, closed, error }
class SyncClient {
WebSocketChannel? _channel;
StreamSubscription? _sub;
final StreamController<RoomBroadcast> _rooms = StreamController.broadcast();
final StreamController<StateBroadcast> _states = StreamController.broadcast();
final StreamController<FeedMessage> _feeds = StreamController.broadcast();
final StreamController<String> _kicked = StreamController.broadcast();
final StreamController<String> _errors = StreamController.broadcast();
final StreamController<SyncStatus> _status = StreamController.broadcast();
Stream<RoomBroadcast> get rooms => _rooms.stream;
Stream<StateBroadcast> get states => _states.stream;
Stream<FeedMessage> get feeds => _feeds.stream;
Stream<String> get kicked => _kicked.stream;
Stream<String> get errors => _errors.stream;
@@ -24,7 +24,7 @@ class SyncClient {
String token = '';
String memberId = '';
String? mediaUrl;
RoomBroadcast? lastRoom;
StateBroadcast? lastState;
Completer<WelcomeMessage>? _pendingWelcome;
Future<WelcomeMessage> connect({
@@ -33,6 +33,7 @@ class SyncClient {
required String name,
String avatar = '',
HelloMedia? media,
String hostKey = '',
}) async {
await disconnect();
_status.add(SyncStatus.connecting);
@@ -61,6 +62,7 @@ class SyncClient {
secret: secret,
user: HelloUser(name: name, avatar: avatar),
media: media ?? const HelloMedia(name: '', size: 0, duration: 0),
hostKey: hostKey,
);
ch.sink.add(jsonEncode(hello.toJson()));
_pendingWelcome = welcomeCompleter;
@@ -74,8 +76,8 @@ class SyncClient {
mediaUrl = welcome.mediaUrl;
if (welcome.state != null) {
try {
lastRoom = RoomBroadcast.fromJson(welcome.state!);
_rooms.add(lastRoom!);
lastState = StateBroadcast.fromJson(welcome.state!);
_states.add(lastState!);
} catch (_) {}
}
_status.add(SyncStatus.open);
@@ -113,11 +115,11 @@ class SyncClient {
return;
}
switch (t) {
case 'room':
case 'state':
try {
final room = RoomBroadcast.fromJson(decoded);
lastRoom = room;
_rooms.add(room);
final state = StateBroadcast.fromJson(decoded);
lastState = state;
_states.add(state);
} catch (_) {}
case 'feed':
try {
@@ -219,12 +221,12 @@ class SyncClient {
token = '';
memberId = '';
mediaUrl = null;
lastRoom = null;
lastState = null;
}
Future<void> dispose() async {
await disconnect();
await _rooms.close();
await _states.close();
await _feeds.close();
await _kicked.close();
await _errors.close();