> */ private array $events = []; public function __construct( private readonly string $socketPath, private readonly float $timeout = 5.0, ) {} /** * Build a client on an already-open stream (tests use a socket pair). * * @param resource $stream */ public static function fromStream($stream): self { $client = new self(''); $client->stream = $stream; return $client; } /** * Send one command and wait for its reply. Throws on transport errors and * on `ok:false` replies. * * @param array $params * @return array the decoded reply (includes `ok` => true) */ public function request(string $cmd, array $params = []): array { $id = $this->nextId++; $stream = $this->stream(); $json = json_encode(['id' => $id, 'cmd' => $cmd] + $params, JSON_UNESCAPED_SLASHES); if (@fwrite($stream, $json."\n") === false) { $this->close(); throw new SidecarException("sidecar write failed ({$cmd})"); } while (true) { $line = @fgets($stream); if ($line === false) { $this->close(); throw new SidecarException("sidecar closed or timed out awaiting the {$cmd} reply"); } $reply = json_decode(trim($line), true); if (! is_array($reply)) { continue; } if (isset($reply['event'])) { $this->events[] = $reply; continue; } if (($reply['id'] ?? null) !== $id) { continue; } if (($reply['ok'] ?? false) !== true) { throw new SidecarException((string) ($reply['error'] ?? "sidecar error ({$cmd})")); } return $reply; } } /** * Notices (peer_connected / peer_left / …) received while waiting for * replies. Clears the buffer. * * @return list> */ public function drainEvents(): array { [$events, $this->events] = [$this->events, []]; return $events; } public function close(): void { if (is_resource($this->stream)) { @fclose($this->stream); } $this->stream = null; } /** @return resource */ private function stream() { if (is_resource($this->stream)) { return $this->stream; } $stream = @stream_socket_client( 'unix://'.$this->socketPath, $errno, $error, $this->timeout, ); if ($stream === false) { throw new SidecarException( "cannot connect to the SFU sidecar at {$this->socketPath}: ".($error ?: "errno {$errno}") ); } $seconds = (int) floor($this->timeout); stream_set_timeout($stream, $seconds, (int) (($this->timeout - $seconds) * 1_000_000)); return $this->stream = $stream; } }