Restructure step 1: lasting identity, presence and protocol 16
Server and core: version ranges on both sides with a coded refusal; a client id maps to the same member and token for the server's life; a drop is `gone` for a 10-minute grace keeping ready, presence, queue items, relay and avatar; `bye` leaves at once; ordered takeover (seq, replaced/stale); presence joining/in/away/gone with one PresencePolicy; joiners never pause playback and hold a held gate at most ~15 s; state.hold; feed ids, seq and replay; the relay lives through its source's grace and continues on re-dial; queue `by` lasting, auto items and cancel; MediaRef carried; client ids never logged. App: persisted client id and seq; Connection with unstable/lost/ replaced states, backoff with jitter, no lost/back pair per flap; a drop keeps the session (state, player, media, relay source); presence in the strip and members card; sit out = away intent; feed matched by id; auto sit-out (idle when not playing, minimised for a while; never when alone) replaces auto-unready; bye on close and Leave, not on update restart; relay source re-dials through its grace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51c1c5ab78
commit
9f2586b7ff
85 files changed
+5198
-1902
No files matched your search
@@ -1,5 +1,3 @@
|
||||
// restructure: rewrite in step 1 (Connection + Identity + Presence): "anyone
|
||||
// sits anyone out" (Away); the rest stays.
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
@@ -25,7 +23,6 @@ class _Peer {
|
||||
proto: RelayConstants.protoVersion,
|
||||
secret: secret,
|
||||
user: HelloUser(name: name),
|
||||
media: const HelloMedia(name: '', size: 0, duration: 0),
|
||||
hostKey: hostKey)
|
||||
.toJson()));
|
||||
await p.next('welcome');
|
||||
@@ -81,6 +78,7 @@ void main() {
|
||||
a.send('{"t":"report","position":1e400,"buffered":1e400,"eventId":0}');
|
||||
final err = await a.next('err');
|
||||
expect('${err['message']}', contains('finite'));
|
||||
expect([err['code'], err['for']], ['bad', 'seek']);
|
||||
expect(server.hub.engine.position, 0);
|
||||
// still alive: a valid seek round-trips through a broadcast
|
||||
a.send('{"t":"seek","position":5}');
|
||||
@@ -125,7 +123,7 @@ void main() {
|
||||
await a.ws.close();
|
||||
});
|
||||
|
||||
test('anyone sits anyone out (host too); nudges reach only the target, capped', () async {
|
||||
test('anyone sets anyone away (host too), only oneself comes back in; nudges reach only the target, capped', () async {
|
||||
await server.stop();
|
||||
server = SyncServer(AppConfig(port: 0, secret: '', name: 't', hostKey: 'hk'));
|
||||
await server.start();
|
||||
@@ -137,19 +135,23 @@ void main() {
|
||||
server.hub.engine.members.values.firstWhere((m) => m.name == name).id;
|
||||
EngineMember of(String name) =>
|
||||
server.hub.engine.members.values.firstWhere((m) => m.name == name);
|
||||
friend.send(jsonEncode(OutIntent(target: idOf('host'), out: true).toJson()));
|
||||
friend.send(jsonEncode(PresenceIntent(target: idOf('host'), state: Presence.away).toJson()));
|
||||
var feed = await friend.next('feed');
|
||||
while (feed['action'] != 'out') {
|
||||
while (feed['action'] != 'away') {
|
||||
feed = await friend.next('feed');
|
||||
}
|
||||
expect([feed['actor'], feed['target']], ['friend', 'host']);
|
||||
expect(of('host').out, isTrue);
|
||||
expect(of('host').inSet, isFalse);
|
||||
expect([feed['actor'], feed['target'], feed['actorId'], feed['targetId']],
|
||||
['friend', 'host', idOf('friend'), idOf('host')]);
|
||||
expect(of('host').presence, Presence.away);
|
||||
expect(server.hub.engine.countsForGate(of('host')), isFalse);
|
||||
// Only oneself comes back.
|
||||
friend.send(jsonEncode(OutIntent(target: idOf('host'), out: false).toJson()));
|
||||
host.send(jsonEncode(OutIntent(target: idOf('host'), out: false).toJson()));
|
||||
friend.send(jsonEncode(PresenceIntent(target: idOf('host'), state: Presence.watching).toJson()));
|
||||
final err = await friend.next('err');
|
||||
expect([err['code'], err['for']], ['forbidden', 'presence']);
|
||||
expect(of('host').presence, Presence.away);
|
||||
host.send(jsonEncode(const PresenceIntent(state: Presence.watching).toJson()));
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
expect(of('host').out, isFalse);
|
||||
expect(of('host').presence, Presence.watching, reason: 'no media up: nothing to load');
|
||||
|
||||
// 5 at once: 3 pass (per second), only to the target.
|
||||
for (var i = 0; i < 5; i++) {
|
||||
@@ -224,8 +226,7 @@ void main() {
|
||||
ws.add(jsonEncode(HelloMessage(
|
||||
proto: RelayConstants.protoVersion,
|
||||
secret: '',
|
||||
user: const HelloUser(name: 'a'),
|
||||
media: const HelloMedia(name: '', size: 0, duration: 0))
|
||||
user: const HelloUser(name: 'a'))
|
||||
.toJson()));
|
||||
final w = await p.next('welcome');
|
||||
expect('${w['mediaUrl']}', startsWith('http://127.0.0.1:${server.boundPort}/m/'));
|
||||
@@ -239,8 +240,7 @@ void main() {
|
||||
ws2.add(jsonEncode(HelloMessage(
|
||||
proto: RelayConstants.protoVersion,
|
||||
secret: '',
|
||||
user: const HelloUser(name: 'c'),
|
||||
media: const HelloMedia(name: '', size: 0, duration: 0))
|
||||
user: const HelloUser(name: 'c'))
|
||||
.toJson()));
|
||||
final w2 = await p2.next('welcome');
|
||||
expect('${w2['mediaUrl']}', startsWith('http://$lan:${server.boundPort}/m/'));
|
||||
|
||||
Reference in new issue
Block a user