Initial commit

This commit is contained in:
2024-09-13 14:11:34 +02:00
commit a14f7077bb
31 changed files with 1901 additions and 0 deletions

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\AuthTypes;
interface Authenticable
{
/**
* Based on auth type, return the right format of credentials to be sent to the server.
*/
public function encodedCredentials(): string;
public function getName(): string;
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\AuthTypes;
use Pdahal\Xmpp\Options;
use Pdahal\Xmpp\Xml\Xml;
abstract class Authentication implements Authenticable
{
use Xml;
protected string $name;
public function __construct(protected Options $options)
{
}
public function getName(): string
{
return $this->name;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\AuthTypes;
class DigestMD5 extends Authentication
{
protected string $name = 'DIGEST-MD5';
public function encodedCredentials(): string
{
$credentials = "\x00{$this->options->getUsername()}\x00{$this->options->getPassword()}";
return self::quote(sha1($credentials));
}
}

16
src/AuthTypes/Plain.php Normal file
View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\AuthTypes;
class Plain extends Authentication
{
protected string $name = 'PLAIN';
public function encodedCredentials(): string
{
$credentials = "\x00{$this->options->getUsername()}\x00{$this->options->getPassword()}";
return self::quote(base64_encode($credentials));
}
}

18
src/Buffers/Buffer.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Buffers;
interface Buffer
{
/**
* Write to buffer (add to array of values)
*/
public function write(string $data): void;
/**
* Read from buffer and delete the data
*/
public function read(): string;
}

35
src/Buffers/Response.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Buffers;
class Response implements Buffer
{
protected array $response = [];
public function write(?string $data = null): void
{
if ($data) {
$this->response[] = $data;
}
}
public function read(): string
{
$implodedResponse = $this->response != [] ? implode('', $this->response) : '';
$this->flush();
return $implodedResponse;
}
protected function flush(): void
{
$this->response = [];
}
public function getCurrentBufferData(): array
{
return $this->response;
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Exceptions;
use Exception;
class DeadSocket extends Exception
{
public function __construct()
{
$errorCode = socket_last_error();
$errorMsg = socket_strerror($errorCode);
parent::__construct("Couldn't create socket: [$errorCode] $errorMsg");
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Exceptions;
use Exception;
class StreamError extends Exception
{
public function __construct(string $streamErrorType)
{
parent::__construct("Unrecoverable stream error ({$streamErrorType}), trying to reconnect...");
}
}

33
src/Loggers/Loggable.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Loggers;
interface Loggable
{
/**
* Standard log message
*/
public function log(string $message): void;
/**
* Shorthand method for logging with prepended "REQUEST" string
*/
public function logRequest(string $message): void;
/**
* Shorthand method for logging with prepended "RESPONSE" string
*/
public function logResponse(string $message): void;
/**
* Shorthand method for logging with prepended "ERROR" string
*/
public function error(string $message): void;
/**
* Returns relative path from given resource
*/
public function getFilePathFromResource(mixed $resource): string;
}

69
src/Loggers/Logger.php Normal file
View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Loggers;
use Exception;
class Logger implements Loggable
{
public mixed $log;
public const LOG_FOLDER = "logs";
public const LOG_FILE = "xmpp.log";
public function __construct()
{
$this->createLogFile();
$this->log = fopen(self::LOG_FOLDER . '/' . self::LOG_FILE, 'a');
}
protected function createLogFile(): void
{
if (!file_exists(self::LOG_FOLDER)) {
mkdir(self::LOG_FOLDER, 0777, true);
}
}
public function log(string $message): void
{
$this->writeToLog($message);
}
public function logRequest(string $message): void
{
$this->writeToLog($message, "REQUEST");
}
public function logResponse(string $message): void
{
$this->writeToLog($message, "RESPONSE");
}
public function error(string $message): void
{
$this->writeToLog($message, "ERROR");
}
protected function writeToLog(string $message, string $type = ''): void
{
$prefix = date("Y.m.d H:i:s") . " " . session_id() . ($type ? " {$type}::" : " ");
$this->writeToFile($this->log, $prefix . "$message\n");
}
protected function writeToFile(mixed $file, string $message): void
{
try {
fwrite($file, $message);
} catch (Exception) {
// silent fail
}
}
public function getFilePathFromResource(mixed $resource): string
{
$metaData = stream_get_meta_data($resource);
return $metaData["uri"];
}
}

259
src/Options.php Normal file
View File

@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp;
use Pdahal\Xmpp\AuthTypes\Authenticable;
use Pdahal\Xmpp\AuthTypes\Plain;
use Pdahal\Xmpp\Loggers\Loggable;
use Pdahal\Xmpp\Loggers\Logger;
use Psr\Log\InvalidArgumentException;
class Options
{
/**
* Hostname of XMPP server.
*/
protected string $host = '';
/**
* Domain Name of XMPP server.
*/
protected string $domain = '';
/**
* XMPP server port. Usually 5222.
*/
protected int $port = 5222;
/**
* Protocol used for socket connection, defaults to TCP.
*/
protected string $protocol = 'tcp';
/**
* Username to authenticate on XMPP server.
*/
protected string $username = '';
/**
* Password to authenticate on XMPP server.
*/
protected string $password = '';
/**
* XMPP resource.
*/
protected string $resource = '';
/**
* Custom logger interface.
*/
protected ?Loggable $logger = null;
/**
* Use TLS if available.
*/
protected bool $useTls = true;
/**
* Auth type (Authentication/AuthTypes/).
*/
protected ?Authenticable $authType = null;
public function getHost(): string
{
if (!$this->host) {
$this->getLogger()->error(__METHOD__.'::'.__LINE__.
' No host found, please set the host variable');
throw new InvalidArgumentException();
}
return $this->host;
}
public function setHost(string $host): Options
{
$this->host = trim($host);
return $this;
}
public function setDomain(string $domain): Options
{
$this->domain = $domain;
return $this;
}
/**
* @return mixed
*/
public function getDomain(): string
{
if ($this->domain == '') {
return $this->getHost();
}
return $this->domain;
}
public function getPort(): int
{
return $this->port;
}
public function setPort(int $port): Options
{
$this->port = $port;
return $this;
}
public function getUsername(): string
{
if ($this->username == '') {
$this->getLogger()->error(__METHOD__.'::'.__LINE__.
' No username found, please set the username variable');
throw new InvalidArgumentException();
}
return $this->username;
}
/**
* Try to assign a resource if it exists. If bare JID is forwarded, this will default to your username.
*/
public function setUsername(string $username): Options
{
$usernameResource = explode('/', $username);
if (count($usernameResource) > 1) {
$this->setResource($usernameResource[1]);
$username = $usernameResource[0];
}
$this->username = trim($username);
return $this;
}
/**
* @throws InvalidArgumentException
*/
public function getPassword(): string
{
if ($this->password == '') {
$this->getLogger()->error(__METHOD__.'::'.__LINE__.
' No password found, please set the password variable');
throw new InvalidArgumentException();
}
return $this->password;
}
public function setPassword(string $password): Options
{
$this->password = $password;
return $this;
}
public function getResource(): string
{
if ($this->resource == '') {
$this->resource = 'Pdahal_machine_'.time();
}
return $this->resource;
}
public function setResource(string $resource): Options
{
$this->resource = trim($resource);
return $this;
}
public function getProtocol(): string
{
return $this->protocol;
}
public function setProtocol(string $protocol): Options
{
$this->protocol = $protocol;
return $this;
}
public function fullSocketAddress(): string
{
$protocol = $this->getProtocol();
$host = $this->getHost();
$port = $this->getPort();
return "{$protocol}://{$host}:{$port}";
}
public function fullJid(): string
{
$username = $this->getUsername();
$resource = $this->getResource();
$host = $this->getHost();
return "{$username}@{$host}/{$resource}";
}
public function bareJid(): string
{
$username = $this->getUsername();
$host = $this->getHost();
return "{$username}@{$host}";
}
public function setLogger(Loggable $logger): void
{
$this->logger = $logger;
}
public function getLogger(): Loggable
{
if ($this->logger === null) {
$this->logger = new Logger();
}
return $this->logger;
}
public function setUseTls(bool $enable): void
{
$this->useTls = $enable;
}
public function usingTls(): bool
{
return $this->useTls;
}
public function getAuthType(): Authenticable
{
if ($this->authType === null) {
$this->setAuthType(new Plain($this));
}
return $this->authType;
}
public function setAuthType(Authenticable $authType): Options
{
$this->authType = $authType;
return $this;
}
}

111
src/Socket.php Normal file
View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp;
use Pdahal\Xmpp\Buffers\Response;
use Pdahal\Xmpp\Exceptions\DeadSocket;
class Socket
{
public mixed $connection;
protected Response $responseBuffer;
protected Options $options;
/**
* Period in microseconds for imposed timeout while doing socket_read().
*/
protected int $timeout = 150000;
/**
* Socket constructor.
*
* @throws DeadSocket
*/
public function __construct(Options $options)
{
$this->responseBuffer = new Response();
$this->connection = stream_socket_client($options->fullSocketAddress());
if (!$this->isAlive($this->connection)) {
throw new DeadSocket();
}
// stream_set_blocking($this->connection, true);
stream_set_timeout($this->connection, 0, $this->timeout);
$this->options = $options;
}
public function disconnect(): void
{
fclose($this->connection);
}
/**
* Sending XML stanzas to open socket.
*/
public function send(string $xml)
{
try {
fwrite($this->connection, $xml);
$this->options->getLogger()->logRequest(__METHOD__.'::'.__LINE__." {$xml}");
// $this->checkSocketStatus();
} catch (\Exception $e) {
$this->options->getLogger()->error(__METHOD__.'::'.__LINE__.' fwrite() failed '.$e->getMessage());
return;
}
$this->receive();
}
public function receive(): void
{
$response = '';
while ($out = fgets($this->connection)) {
$response .= $out;
}
if (!$response) {
return;
}
$this->responseBuffer->write($response);
$this->options->getLogger()->logResponse(__METHOD__.'::'.__LINE__." {$response}");
}
protected function isAlive($socket): bool
{
return $socket !== false;
}
public function setTimeout($timeout): void
{
$this->timeout = $timeout;
}
public function getResponseBuffer(): Response
{
return $this->responseBuffer;
}
public function getOptions(): Options
{
return $this->options;
}
protected function checkSocketStatus(): void
{
$status = socket_get_status($this->connection);
// echo print_r($status);
if ($status['eof']) {
$this->options->getLogger()->logResponse(
__METHOD__.'::'.__LINE__.
" ---Probably a broken pipe, restart connection\n"
);
}
}
}

51
src/Xml/Stanzas/Auth.php Normal file
View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml\Stanzas;
use Pdahal\Xmpp\AuthTypes\Authenticable;
class Auth extends Stanza
{
public function authenticate(): void
{
$response = $this->socket->getResponseBuffer()->read();
$options = $this->socket->getOptions();
$tlsSupported = self::isTlsSupported($response);
$tlsRequired = self::isTlsRequired($response);
if ($tlsSupported && ($tlsRequired || (!$tlsRequired && $options->usingTls()))) {
$this->startTls();
$this->socket->send(self::openXmlStream($options->getDomain()));
}
$xml = $this->generateAuthXml($options->getAuthType());
$this->socket->send($xml);
$this->socket->send(self::openXmlStream($options->getDomain()));
}
protected function startTls(): void
{
$this->socket->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
$response = $this->socket->getResponseBuffer()->read();
if (!self::canProceed($response)) {
$this->socket->getOptions()->getLogger()->error(__METHOD__ . '::' . __LINE__ .
" TLS authentication failed. Trying to continue but will most likely fail.");
return;
}
stream_socket_enable_crypto($this->socket->connection, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
}
protected function generateAuthXml(Authenticable $authType): string
{
$mechanism = $authType->getName();
$encodedCredentials = $authType->encodedCredentials();
$nameSpace = "urn:ietf:params:xml:ns:xmpp-sasl";
return "<auth xmlns='{$nameSpace}' mechanism='{$mechanism}'>{$encodedCredentials}</auth>";
}
}

98
src/Xml/Stanzas/Iq.php Normal file
View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml\Stanzas;
class Iq extends Stanza
{
public function getRoster(): void
{
$query = "<query xmlns='jabber:iq:roster'/>";
$xml = "<iq type='get' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
public function addToRoster(string $name, string $forJid, string $from, ?string $groupName = null): void
{
$group = $groupName ? "<group>{$groupName}</group>" : null;
$item = "<item jid='{$forJid}' name='{$name}'>{$group}</item>";
$query = "<query xmlns='jabber:iq:roster'>{$item}</query>";
$xml = "<iq type='set' id='{$this->uniqueId()}' from='{$from}'>{$query}</iq>";
$this->socket->send($xml);
}
public function removeFromRoster(string $jid, string $myJid): void
{
$item = "<item jid='{$jid}' subscription='remove'/>";
$query = "<query xmlns='jabber:iq:roster'>{$item}</query>";
$xml = "<iq type='set' id='{$this->uniqueId()}' from='{$myJid}'>{$query}</iq>";
$this->socket->send($xml);
}
public function setResource(string $name): void
{
if (!trim($name)) {
return;
}
$resource = "<resource>{$name}</resource>";
$bind = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>{$resource}</bind>";
$xml = "<iq type='set' id='{$this->uniqueId()}'>{$bind}</iq>";
$this->socket->send($xml);
}
public function setGroup(string $name, string $forJid): void
{
$group = "<group>{$name}</group>";
$item = "<item jid='{$forJid}'>{$group}</item>";
$query = "<query xmlns='jabber:iq:roster'>{$item}</query>";
$xml = "<iq type='set' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
public function getServerVersion(): void
{
$query = "<query xmlns='jabber:iq:version'/>";
$xml = "<iq type='get' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
public function getServerFeatures(): void
{
$query = "<query xmlns='http://jabber.org/protocol/disco#info'></query>";
$xml = "<iq type='get' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
public function getServerTime(): void
{
$query = "<query xmlns='urn:xmpp:time'/>";
$xml = "<iq type='get' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
public function getFeatures(string $forJid): void
{
$query = "<query xmlns='http://jabber.org/protocol/disco#info'></query>";
$xml = "<iq type='get' to='{$forJid}'>{$query}</iq>";
$this->socket->send($xml);
}
public function ping(): void
{
$query = "<query xmlns='urn:xmpp:ping'/>";
$xml = "<iq type='get' id='{$this->uniqueId()}'>{$query}</iq>";
$this->socket->send($xml);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml\Stanzas;
class Message extends Stanza
{
public function send(string $body, string $to, string $type = "chat"): void
{
$xml = $this->generateMessageXml($body, $to, $type);
$this->socket->send($xml);
}
public function receive(): array
{
$this->socket->receive();
$rawResponse = $this->socket->getResponseBuffer()->read();
return self::parseTag($rawResponse, "message");
}
protected function generateMessageXml(string $body, string $to, string $type): string
{
$to = self::quote($to);
$body = self::quote($body);
$bodyXml = "<body>{$body}</body>";
return "<message to='{$to}' type='{$type}'>{$bodyXml}</message>";
}
}

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml\Stanzas;
class Presence extends Stanza
{
public const PRIORITY_UPPER_BOUND = 127;
public const PRIORITY_LOWER_BOUND = -128;
public function subscribe(string $to): void
{
$this->setPresence($to, 'subscribe');
}
public function unsubscribe(string $from): void
{
$this->setPresence($from, 'unsubscribe');
}
public function acceptSubscription(string $from): void
{
$this->setPresence($from, 'subscribed');
}
public function declineSubscription(string $from): void
{
$this->setPresence($from, 'unsubscribed');
}
protected function setPresence(string $to, string $type = "subscribe"): void
{
$xml = "<presence from='{$this->socket->getOptions()->bareJid()}' to='{$to}' type='{$type}'/>";
$this->socket->send($xml);
}
/**
* Set priority to current resource by default, or optional other resource tied to the
* current username
* @param string|null $forResource
*/
public function setPriority(int $value, string $forResource = null): void
{
$from = self::quote($this->socket->getOptions()->fullJid());
if ($forResource) {
$from = $this->socket->getOptions()->getUsername() . "/$forResource";
}
$priority = "<priority>{$this->limitPriority($value)}</priority>";
$xml = "<presence from='{$from}'>{$priority}</presence>";
$this->socket->send($xml);
}
protected function limitPriority(int $value): int
{
if ($value > self::PRIORITY_UPPER_BOUND) {
return self::PRIORITY_UPPER_BOUND;
} elseif ($value < self::PRIORITY_LOWER_BOUND) {
return self::PRIORITY_LOWER_BOUND;
}
return $value;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml\Stanzas;
use Pdahal\Xmpp\Socket;
use Pdahal\Xmpp\Xml\Xml;
abstract class Stanza
{
use Xml;
public function __construct(protected Socket $socket)
{
}
protected function uniqueId(): string
{
return uniqid();
}
protected function readResponseFile(): string|false
{
$logger = $this->socket->getOptions()->getLogger();
$responseFilePath = $logger->getFilePathFromResource($logger->log);
$responseFile = fopen($responseFilePath, 'r');
return fread($responseFile, filesize($responseFilePath));
}
}

123
src/Xml/Xml.php Normal file
View File

@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp\Xml;
use Pdahal\Xmpp\Exceptions\StreamError;
trait Xml
{
/**
* Opening tag for starting a XMPP stream exchange.
*/
public static function openXmlStream(string $host): string
{
$xmlOpen = "<?xml version='1.0' encoding='UTF-8'?>";
$to = "to='{$host}'";
$stream = "xmlns:stream='http://etherx.jabber.org/streams'";
$client = "xmlns='jabber:client'";
$version = "version='1.0'";
return "{$xmlOpen}<stream:stream {$to} {$stream} {$client} {$version}>";
}
/**
* Closing tag for one XMPP stream session.
*/
public static function closeXmlStream(): string
{
return '</stream:stream>';
}
public static function quote(string $input): string
{
return htmlspecialchars($input, ENT_XML1, 'utf-8');
}
public static function parseTag(string $rawResponse, string $tag): array
{
preg_match_all("#(<{$tag}.*?>.*?<\\/{$tag}>)#si", $rawResponse, $matched);
return count($matched) <= 1 ? [] : array_map(fn ($match): false|\SimpleXMLElement => @simplexml_load_string($match), $matched[1]);
}
public static function parseFeatures(string $xml): string
{
return self::matchInsideOfTag($xml, 'stream:features');
}
public static function isTlsSupported(string $xml): bool
{
$matchTag = self::matchCompleteTag($xml, 'starttls');
return !empty($matchTag);
}
public static function isTlsRequired(string $xml): bool
{
if (!self::isTlsSupported($xml)) {
return false;
}
$tls = self::matchCompleteTag($xml, 'starttls');
preg_match('#required#', $tls, $match);
return count($match) > 0;
}
public static function matchCompleteTag(string $xml, string $tag): string
{
$matches = self::matchTag($xml, $tag);
return is_array($matches) && count($matches) > 0 ? $matches[0] : '';
}
public static function matchInsideOfTag(string $xml, string $tag): string
{
$match = self::matchTag($xml, $tag);
return is_array($match) && count($match) > 1 ? $match[1] : '';
}
private static function matchTag(string $xml, string $tag): array
{
$matches = null;
preg_match("#<{$tag}.*?>(.*)<\\/{$tag}>#", $xml, $matches);
return count($matches) < 1 ? [] : $matches;
}
public static function canProceed($xml): bool
{
preg_match("#<proceed xmlns=[\\'|\"]urn:ietf:params:xml:ns:xmpp-tls[\\'|\"]\\/>#", (string) $xml, $match);
return count($match) > 0;
}
public static function supportedAuthMethods($xml): array
{
preg_match_all('#<mechanism>(.*?)<\\/mechanism>#', (string) $xml, $match);
return count($match) < 1 ? [] : $match[1];
}
public static function roster(string $xml): array
{
preg_match_all("#<iq.*?type=[\\'|\"]result[\\'|\"]>(.*?)<\\/iq>#", $xml, $match);
return count($match) < 1 ? [] : $match[1];
}
/**
* @throws StreamError
*/
public static function checkForUnrecoverableErrors(string $response): void
{
preg_match_all('#<stream:error>(<(.*?) (.*?)\\/>)<\\/stream:error>#', $response, $streamErrors);
if ((!empty($streamErrors[0])) && count($streamErrors[2]) > 0) {
throw new StreamError($streamErrors[2][0]);
}
}
}

131
src/XmppClient.php Normal file
View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Pdahal\Xmpp;
use Pdahal\Xmpp\Exceptions\StreamError;
use Pdahal\Xmpp\Xml\Stanzas\Auth;
use Pdahal\Xmpp\Xml\Stanzas\Iq;
use Pdahal\Xmpp\Xml\Stanzas\Message;
use Pdahal\Xmpp\Xml\Stanzas\Presence;
use Pdahal\Xmpp\Xml\Xml;
class XmppClient
{
use Xml;
protected Socket $socket;
public Auth $auth;
public Iq $iq;
public Presence $presence;
public Message $message;
public function __construct(protected Options $options)
{
$this->initDependencies();
$this->initSession();
}
protected function initDependencies(): void
{
$this->socket = $this->initSocket();
$this->initStanzas($this->socket);
}
public function connect(): void
{
$this->openStream();
$this->auth->authenticate();
$this->iq->setResource($this->options->getResource());
$this->sendInitialPresenceStanza();
}
public function send(string $xml): void
{
$this->socket->send($xml);
}
public function getResponse(): string
{
$this->socket->receive();
$response = $this->socket->getResponseBuffer()->read();
return $this->checkForErrors($response);
}
public function prettyPrint(string $response): void
{
if ($response) {
$separator = "\n-------------\n";
echo "{$separator} {$response} {$separator}";
}
}
public function disconnect(): void
{
$this->socket->send(self::closeXmlStream());
$this->socket->disconnect();
}
protected function openStream(): void
{
$openStreamXml = self::openXmlStream($this->options->getDomain());
$this->socket->send($openStreamXml);
}
protected function sendInitialPresenceStanza(): void
{
$this->socket->send('<presence/>');
}
protected function initStanzas(Socket $socket): void
{
$this->auth = new Auth($socket);
$this->iq = new Iq($socket);
$this->presence = new Presence($socket);
$this->message = new Message($socket);
}
protected function initSession(): void
{
if (!headers_sent($filename, $linenum)) {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
}
protected function initSocket(): Socket
{
return new Socket($this->options);
}
/**
* @throws StreamError
*/
protected function checkForErrors(string $response): string
{
try {
self::checkForUnrecoverableErrors($response);
} catch (StreamError $e) {
$this->options->getLogger()->logResponse(__METHOD__.'::'.__LINE__." {$response}");
$this->options->getLogger()->error(__METHOD__.'::'.__LINE__.' '.$e->getMessage());
$this->reconnect();
$response = '';
}
return $response;
}
protected function reconnect(): void
{
$this->disconnect();
$this->initDependencies();
$this->connect();
}
}