laravel-webrtc/tests/RelaySignalingHandlerTest.php

210 lines
7.8 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Blax\WebRtc\Tests;
use Blax\WebRtc\Signaling\RelaySignalingHandler;
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
use Blax\WebRtc\Tests\Fixtures\AllowAuthorizer;
use Blax\WebRtc\Tests\Fixtures\CollectingEvents;
use Blax\WebRtc\Tests\Fixtures\FakePeer;
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
use Blax\WebRtc\Tests\Fixtures\MemoryRecordingStore;
use PHPUnit\Framework\TestCase;
final class RelaySignalingHandlerTest extends TestCase
{
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_welcomes_a_peer_with_its_id_and_the_record_flag(): void
{
$a = new FakePeer('a');
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
(new RelaySignalingHandler)->onOpen($a);
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$this->assertSame(['type' => 'welcome', 'peer' => 'a', 'record' => false], $a->lastJson());
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_public_join_returns_others_notifies_them_and_has_no_member_list(): void
{
$handler = new RelaySignalingHandler;
$a = new FakePeer('a');
$b = new FakePeer('b');
$handler->onOpen($a);
$handler->onOpen($b);
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby']));
$handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'lobby']));
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$joined = $b->lastJson();
$this->assertSame('joined', $joined['type']);
$this->assertSame('public', $joined['roomType']);
$this->assertSame(['a'], $joined['peers']);
$this->assertArrayNotHasKey('members', $joined);
$this->assertContains(['type' => 'peer-joined', 'room' => 'lobby', 'peer' => 'b'], $a->jsonMessages());
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_presence_room_shares_the_member_list_with_info(): void
{
$handler = new RelaySignalingHandler;
$a = new FakePeer('a');
$b = new FakePeer('b');
$handler->onOpen($a);
$handler->onOpen($b);
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'open-presence-lobby', 'info' => ['name' => 'Ann']]));
$handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'open-presence-lobby', 'info' => ['name' => 'Bob']]));
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$joined = $b->lastJson();
$this->assertSame('open-presence', $joined['roomType']);
$this->assertSame(['a' => ['name' => 'Ann'], 'b' => ['name' => 'Bob']], $joined['members']);
$this->assertContains(
['type' => 'peer-joined', 'room' => 'open-presence-lobby', 'peer' => 'b', 'info' => ['name' => 'Bob']],
$a->jsonMessages(),
);
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_private_room_is_denied_by_default(): void
{
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$handler = new RelaySignalingHandler; // DefaultRoomAuthorizer denies
$a = new FakePeer('a');
$handler->onOpen($a);
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'private-x']));
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$this->assertSame(['type' => 'error', 'error' => 'unauthorized'], $a->lastJson());
$this->assertNull($handler->rooms()->roomOf('a'));
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_private_room_is_allowed_by_a_bound_authorizer(): void
{
$handler = new RelaySignalingHandler(authorizer: new AllowAuthorizer(['role' => 'host']));
$a = new FakePeer('a');
$handler->onOpen($a);
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'private-x', 'auth' => ['token' => 't']]));
$joined = $a->lastJson();
$this->assertSame('joined', $joined['type']);
$this->assertSame('private', $joined['roomType']);
$this->assertSame('private-x', $handler->rooms()->roomOf('a'));
}
public function test_relay_forwards_to_the_target_with_the_sender(): void
{
$handler = new RelaySignalingHandler;
$a = new FakePeer('a');
$b = new FakePeer('b');
$handler->onOpen($a);
$handler->onOpen($b);
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$handler->onMessage($a, $this->json(['type' => 'relay', 'to' => 'b', 'data' => ['sdp' => 'OFFER']]));
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$this->assertSame(['type' => 'relay', 'from' => 'a', 'data' => ['sdp' => 'OFFER']], $b->lastJson());
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_leave_notifies_the_remaining_peers(): void
{
$handler = new RelaySignalingHandler;
$a = new FakePeer('a');
$b = new FakePeer('b');
$handler->onOpen($a);
$handler->onOpen($b);
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby']));
$handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'lobby']));
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$handler->onMessage($b, $this->json(['type' => 'leave']));
$this->assertContains(['type' => 'peer-left', 'peer' => 'b'], $a->jsonMessages());
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
$this->assertSame(['type' => 'left'], $b->lastJson());
}
public function test_broadcast_reaches_the_rest_of_the_room_only(): void
{
$handler = new RelaySignalingHandler;
$peers = [new FakePeer('a'), new FakePeer('b'), new FakePeer('c')];
foreach ($peers as $p) {
$handler->onOpen($p);
$handler->onMessage($p, $this->json(['type' => 'join', 'room' => 'r']));
}
[$a, $b, $c] = $peers;
$handler->onMessage($a, $this->json(['type' => 'broadcast', 'data' => ['x' => 1]]));
$expected = ['type' => 'broadcast', 'from' => 'a', 'data' => ['x' => 1]];
$this->assertContains($expected, $b->jsonMessages());
$this->assertContains($expected, $c->jsonMessages());
$this->assertNotContains($expected, $a->jsonMessages());
}
feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
public function test_streamed_binary_audio_is_recorded_and_finalized_on_disconnect(): void
{
$store = new MemoryRecordingStore;
$events = new CollectingEvents;
$handler = new RelaySignalingHandler(recorder: $store, events: $events);
$a = new FakePeer('a');
$handler->onOpen($a);
$this->assertTrue($a->lastJson()['record'], 'welcome should tell the client to record');
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby']));
$handler->onBinary($a, 'AUDIO-1');
$handler->onBinary($a, 'AUDIO-2');
$this->assertSame('AUDIO-1AUDIO-2', $store->data['lobby|a']);
$handler->onClose($a);
$this->assertContains('lobby|a', $store->finalized);
$finalized = $events->ofType('recording-finalized');
$this->assertCount(1, $finalized);
$this->assertSame('mem://lobby|a', $finalized[0]['recording']);
}
public function test_binary_without_a_recorder_is_ignored(): void
{
$handler = new RelaySignalingHandler; // no recorder
$a = new FakePeer('a');
$handler->onOpen($a);
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby']));
$handler->onBinary($a, 'AUDIO'); // must be a harmless no-op
$this->assertTrue(true);
}
public function test_emits_ordered_call_lifecycle_events(): void
{
$events = new CollectingEvents;
$handler = new RelaySignalingHandler(events: $events);
$a = new FakePeer('a');
$handler->onOpen($a);
$handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby']));
$handler->onClose($a);
$types = $events->types();
$this->assertContains('peer-connected', $types);
$this->assertContains('room-joined', $types);
$this->assertContains('room-left', $types);
$this->assertContains('peer-disconnected', $types);
$seqs = array_column($events->events, 'seq');
$this->assertSame(range(1, count($seqs)), $seqs, 'seq should be monotonic from 1');
}
public function test_invalid_json_and_unknown_type_are_errors(): void
{
$handler = new RelaySignalingHandler;
$a = new FakePeer('a');
$handler->onOpen($a);
$handler->onMessage($a, 'not json');
$this->assertSame('error', $a->lastJson()['type']);
$handler->onMessage($a, $this->json(['type' => 'wat']));
$this->assertSame('error', $a->lastJson()['type']);
}
/** @param array<string,mixed> $data */
private function json(array $data): string
{
return (string) json_encode($data);
}
}