From 23c7d9f4d6459f215e34e92cff3ed1df65bf9852 Mon Sep 17 00:00:00 2001 From: soneill Date: Sun, 30 Aug 2020 20:03:09 +1200 Subject: [PATCH] private fork from github --- pathfinder_websocket | 1 + websocket/.gitattributes | 17 - websocket/.gitignore | 11 - websocket/README.md | 134 -- .../Component/AbstractMessageComponent.php | 269 ---- .../Formatter/SubscriptionFormatter.php | 41 - .../app/Component/Handler/LogFileHandler.php | 76 -- websocket/app/Component/MapUpdate.php | 943 -------------- websocket/app/Data/Payload.php | 94 -- websocket/app/Log/ShellColors.php | 90 -- websocket/app/Log/Store.php | 220 ---- websocket/app/Socket/AbstractSocket.php | 67 - websocket/app/Socket/TcpSocket.php | 551 -------- websocket/app/WebSockets.php | 113 -- websocket/cmd.php | 91 -- websocket/composer.json | 27 - websocket/composer.lock | 1120 ----------------- 17 files changed, 1 insertion(+), 3864 deletions(-) create mode 160000 pathfinder_websocket delete mode 100644 websocket/.gitattributes delete mode 100644 websocket/.gitignore delete mode 100644 websocket/README.md delete mode 100644 websocket/app/Component/AbstractMessageComponent.php delete mode 100644 websocket/app/Component/Formatter/SubscriptionFormatter.php delete mode 100644 websocket/app/Component/Handler/LogFileHandler.php delete mode 100644 websocket/app/Component/MapUpdate.php delete mode 100644 websocket/app/Data/Payload.php delete mode 100644 websocket/app/Log/ShellColors.php delete mode 100644 websocket/app/Log/Store.php delete mode 100644 websocket/app/Socket/AbstractSocket.php delete mode 100644 websocket/app/Socket/TcpSocket.php delete mode 100644 websocket/app/WebSockets.php delete mode 100644 websocket/cmd.php delete mode 100644 websocket/composer.json delete mode 100644 websocket/composer.lock diff --git a/pathfinder_websocket b/pathfinder_websocket new file mode 160000 index 0000000..505d3d1 --- /dev/null +++ b/pathfinder_websocket @@ -0,0 +1 @@ +Subproject commit 505d3d163e9801835aeb508971a9b50278f0404b diff --git a/websocket/.gitattributes b/websocket/.gitattributes deleted file mode 100644 index bdb0cab..0000000 --- a/websocket/.gitattributes +++ /dev/null @@ -1,17 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.cs diff=csharp - -# Standard to msysgit -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain diff --git a/websocket/.gitignore b/websocket/.gitignore deleted file mode 100644 index 1f1701e..0000000 --- a/websocket/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Created by .ignore support plugin (hsz.mobi) -### Example user template template -### Example user template - -# IntelliJ project files -.idea -out -gen - -vendor - diff --git a/websocket/README.md b/websocket/README.md deleted file mode 100644 index fc098f1..0000000 --- a/websocket/README.md +++ /dev/null @@ -1,134 +0,0 @@ -## WebSocket server for [Pathfinder](https://github.com/exodus4d/pathfinder) - -### Requirements -- _PHP_ (≥ v7.1) -- A working instance of *[Pathfinder](https://github.com/exodus4d/pathfinder)* (≥ v2.0.0-rc.1) -- [_Composer_](https://getcomposer.org/download/) to install packages for the WebSocket server - -### Install -1. Checkout this project in a **new** folder e.g. `/var/www/websocket.pathfinder` -1. Install [_Composer_](https://getcomposer.org/download/) -2. Install Composer dependencies from `composer.json` file: - - `$ cd /var/www/websocket.pathfinder` - - `$ composer install` -3. Start WebSocket server `$ php cmd.php` - -### Configuration - -#### Default - -**Clients (WebBrowser) listen for connections** -- Host: `0.0.0.0.` (=> any client can connect) -- Port: `8020` -- ↪ URI: `127.0.0.1:8020` - -(=> Your WebServer (e.g. Nginx) should proxy all WebSocket connections to this source) - -**TCP TcpSocket connection (Internal use for WebServer ⇄ WebSocket server communication)** -- Host: `127.0.0.1` (=> Assumed WebServer and WebSocket server running on the same machine) -- Port: `5555` -- ↪ URI: `tcp://127.0.0.1:5555` - -(=> Where _Pathfinder_ reaches the WebSocket server. This must match `SOCKET_HOST`, `SOCKET_PORT` options in `environment.ini`) - -#### Start parameters [Optional] - -The default configuration should be fine for most installations. -You can change/overwrite the default **Host** and **Port** configuration by adding additional CLI parameters when starting the WebSocket server: - -`$ php cmd.php --wsHost [CLIENTS_HOST] --wsPort [CLIENTS_PORT] --tcpHost [TCP_HOST] --tcpPort [TCP_PORT] --debug 0` - - For example: If you want to change the the WebSocket port and increase debug output: - - `$ php cmd.php --wsPort 8030 --debug 3` - -##### --debug (default `--debug 2`) - -Allows you to set log output level from `0` (silent) - errors are not logged, to `3` (debug) for detailed logging. - -![alt text](https://i.imgur.com/KfNF4lk.png) - -### WebSocket UI - -There is a WebSocket section on _Pathinders_ `/setup` page. After the WebSocket server is started, you should check it if everything works. -You see the most recent WebSocket log entries, the current connection state, the current number of active connections and all maps that have subscriptions - -![alt text](https://i.imgur.com/dDUrnx2.png) - -Log entry view. Depending on the `--debug` parameter, the most recent (max 50) entries will be shown: - -![alt text](https://i.imgur.com/LIn9aNm.png) - -Subscriptions for each map: - -![alt text](https://i.imgur.com/fANYwho.gif) - -### Unix Service (systemd) - -#### New Service -It is recommended to wrap the `cmd.php` script in a Unix service, that over control the WebSocket server. -This creates a systemd service on CentOS7: -1. `$ cd /etc/systemd/system` -2. `$ vi websocket.pathfinder.service` -3. Copy script and adjust `ExecStart` and `WorkingDirectory` values: - -``` -[Unit] -Description = WebSocket server (Pathfinder) [LIVE] environment -After = multi-user.target - -[Service] -Type = idle -ExecStart = /usr/bin/php /var/www/websocket.pathfinder/pathfinder_websocket/cmd.php -WorkingDirectory = /var/www/websocket.pathfinder/pathfinder_websocket -TimeoutStopSec = 0 -Restart = always -LimitNOFILE = 10000 -Nice = 10 - -[Install] -WantedBy = multi-user.target -``` - -Now you can use the service to start/stop/restart your WebSocket server -- `$ systemctl start websocket.pathfinder.service` -- `$ systemctl restart websocket.pathfinder.service` -- `$ systemctl stop websocket.pathfinder.service` - -#### Auto-Restart the Service -You can automatically restart your service (e.g. on _EVE-Online_ downtime). Create a new "timer" for the automatic restart. -1. `$ cd /etc/systemd/system` (same dir as before) -2. `$ vi restart.websocket.pathfinder.timer` -3. Copy script: - -``` -[Unit] -Description = Restart timer (EVE downtime) for WebSocket server [LIVE] - -[Timer] -OnCalendar = *-*-* 12:01:00 -Persistent = true - -[Install] -WantedBy = timer.target -``` -Now we need a new "restart service" for the timer: -1. `$ cd /etc/systemd/system` (same dir as before) -2. `$ vi restart.websocket.pathfinder.service` -3. Copy script: - -``` -[Unit] -Description = Restart (periodically) WebSocket server [LIVE] - -[Service] -Type = oneshot -ExecStart = /usr/bin/systemctl try-restart websocket.pathfinder.service -``` -And then, we need to either restart the machine or launch -``` -systemctl start restart.websocket.pathfinder.timer -``` -### Info -- [*Ratchet*](http://socketo.me) - "WebSockets for PHP" -- [*ReactPHP*](https://reactphp.org) - "Event-driven, non-blocking I/O with PHP" diff --git a/websocket/app/Component/AbstractMessageComponent.php b/websocket/app/Component/AbstractMessageComponent.php deleted file mode 100644 index 299fb4b..0000000 --- a/websocket/app/Component/AbstractMessageComponent.php +++ /dev/null @@ -1,269 +0,0 @@ - should be overwritten in child instances - * -> is used as "log store" name - */ - const COMPONENT_NAME = 'default'; - - /** - * log message server start - */ - const LOG_TEXT_SERVER_START = 'start WebSocket server…'; - - /** - * store for logs - * @var Store - */ - protected $logStore; - - /** - * stores all active connections - * -> regardless of its subscription state - * [ - * '$conn1->resourceId' => [ - * 'connection' => $conn1, - * 'data' => null - * ], - * '$conn2->resourceId' => [ - * 'connection' => $conn2, - * 'data' => null - * ] - * ] - * @var array - */ - private $connections; - - /** - * max count of concurrent open connections - * @var int - */ - private $maxConnections = 0; - - /** - * AbstractMessageComponent constructor. - * @param Store $store - */ - public function __construct(Store $store){ - $this->connections = []; - $this->logStore = $store; - - $this->log(['debug', 'info'], null, 'START', static::LOG_TEXT_SERVER_START); - } - - // Connection callbacks from MessageComponentInterface ============================================================ - - /** - * new client connection onOpen - * @param ConnectionInterface $conn - */ - public function onOpen(ConnectionInterface $conn){ - $this->log(['debug'], $conn, __FUNCTION__, 'open connection'); - - $this->addConnection($conn); - } - - /** - * client connection onClose - * @param ConnectionInterface $conn - */ - public function onClose(ConnectionInterface $conn){ - $this->log(['debug'], $conn, __FUNCTION__, 'close connection'); - - $this->removeConnection($conn); - } - - /** - * client connection onError - * @param ConnectionInterface $conn - * @param \Exception $e - */ - public function onError(ConnectionInterface $conn, \Exception $e){ - $this->log(['debug', 'error'], $conn, __FUNCTION__, $e->getMessage()); - } - - /** - * new message received from client connection - * @param ConnectionInterface $conn - * @param string $msg - */ - public function onMessage(ConnectionInterface $conn, $msg){ - // parse message into payload object - $payload = $this->getPayloadFromMessage($msg); - - if($payload){ - $this->dispatchWebSocketPayload($conn, $payload); - } - } - - // Connection handling ============================================================================================ - - /** - * add connection - * @param ConnectionInterface $conn - */ - private function addConnection(ConnectionInterface $conn) : void { - $this->connections[$conn->resourceId] = [ - 'connection' => $conn, - ]; - - $this->maxConnections = max(count($this->connections), $this->maxConnections); - } - - /** - * remove connection - * @param ConnectionInterface $conn - */ - private function removeConnection(ConnectionInterface $conn) : void { - if($this->hasConnection($conn)){ - unset($this->connections[$conn->resourceId]); - } - } - - /** - * @param ConnectionInterface $conn - * @return bool - */ - protected function hasConnection(ConnectionInterface $conn) : bool { - return isset($this->connections[$conn->resourceId]); - } - - /** - * @param int $resourceId - * @return bool - */ - protected function hasConnectionId(int $resourceId) : bool { - return isset($this->connections[$resourceId]); - } - - /** - * @param int $resourceId - * @return ConnectionInterface|null - */ - protected function getConnection(int $resourceId) : ?ConnectionInterface { - return $this->hasConnectionId($resourceId) ? $this->connections[$resourceId]['connection'] : null; - } - - /** - * update meta data for $conn - * @param ConnectionInterface $conn - */ - protected function updateConnection(ConnectionInterface $conn){ - if($this->hasConnection($conn)){ - $meta = [ - 'mTimeSend' => microtime(true) - ]; - $this->connections[$conn->resourceId]['data'] = array_merge($this->getConnectionData($conn), $meta); - } - } - - /** - * get meta data from $conn - * @param ConnectionInterface $conn - * @return array - */ - protected function getConnectionData(ConnectionInterface $conn) : array { - $meta = []; - if($this->hasConnection($conn)){ - $meta = (array)$this->connections[$conn->resourceId]['data']; - } - return $meta; - } - - /** - * wrapper for ConnectionInterface->send() - * -> this stores some meta data to the $conn - * @param ConnectionInterface $conn - * @param $data - */ - protected function send(ConnectionInterface $conn, $data){ - $conn->send($data); - $this->updateConnection($conn); - } - - /** - * @param ConnectionInterface $conn - * @param Payload $payload - */ - abstract protected function dispatchWebSocketPayload(ConnectionInterface $conn, Payload $payload) : void; - - /** - * get Payload class from client message - * @param mixed $msg - * @return Payload|null - */ - protected function getPayloadFromMessage($msg) : ?Payload { - $payload = null; - $msg = (array)json_decode($msg, true); - - if(isset($msg['task'], $msg['load'])){ - $payload = $this->newPayload((string)$msg['task'], $msg['load']); - } - - return $payload; - } - - /** - * @param string $task - * @param null $load - * @param array|null $characterIds - * @return Payload|null - */ - protected function newPayload(string $task, $load = null, ?array $characterIds = null) : ?Payload { - $payload = null; - try{ - $payload = new Payload($task, $load, $characterIds); - }catch(\Exception $e){ - $this->log(['debug', 'error'], null, __FUNCTION__, $e->getMessage()); - } - - return $payload; - } - - /** - * get WebSocket stats data - * @return array - */ - public function getSocketStats() : array { - return [ - 'connections' => count($this->connections), - 'maxConnections' => $this->maxConnections, - 'logs' => array_reverse($this->logStore->getStore()) - ]; - } - - /** - * @param $logTypes - * @param ConnectionInterface|null $connection - * @param string $action - * @param string $message - */ - protected function log($logTypes, ?ConnectionInterface $connection, string $action, string $message = '') : void { - if($this->logStore){ - $remoteAddress = $connection ? $connection->remoteAddress : null; - $resourceId = $connection ? $connection->resourceId : null; - $this->logStore->log($logTypes, $remoteAddress, $resourceId, $action, $message); - } - } - - /** - * - * @param TimerInterface $timer - */ - public function housekeeping(TimerInterface $timer) : void { - - } -} \ No newline at end of file diff --git a/websocket/app/Component/Formatter/SubscriptionFormatter.php b/websocket/app/Component/Formatter/SubscriptionFormatter.php deleted file mode 100644 index f1e5972..0000000 --- a/websocket/app/Component/Formatter/SubscriptionFormatter.php +++ /dev/null @@ -1,41 +0,0 @@ - $characterData){ - // check if characterData has an active log (active system for character) - $systemId = 0; - if(isset($characterData['log']['system']['id'])){ - $systemId = (int)$characterData['log']['system']['id']; - } - - if( !isset($data[$systemId]) ){ - $systemData = (object)[]; - $systemData->id = $systemId; - $data[$systemId] = $systemData; - } - - $data[$systemId]->user[] = $characterData; - } - $data = array_values($data); - - return $data; - } - -} \ No newline at end of file diff --git a/websocket/app/Component/Handler/LogFileHandler.php b/websocket/app/Component/Handler/LogFileHandler.php deleted file mode 100644 index a47ff7b..0000000 --- a/websocket/app/Component/Handler/LogFileHandler.php +++ /dev/null @@ -1,76 +0,0 @@ -stream = $stream; - $this->dir = dirname($this->stream); - $this->createDir(); - } - - /** - * write log data into to file - * @param array $log - */ - public function write(array $log){ - $log = (string)json_encode($log, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - if( !empty($log) ){ - if($stream = fopen($this->stream, 'a')){ - flock($stream, LOCK_EX); - fwrite($stream, $log . PHP_EOL); - flock($stream, LOCK_UN); - fclose($stream); - - // logs should be writable for non webSocket user too - @chmod($this->stream, 0666); - } - } - } - - /** - * create directory - */ - private function createDir(){ - // Do not try to create dir if it has already been tried. - if ($this->dirCreated){ - return; - } - - if ($this->dir && !is_dir($this->dir)){ - $status = mkdir($this->dir, 0777, true); - if (false === $status) { - throw new \UnexpectedValueException(sprintf(self::ERROR_DIR_CREATE, $this->dir)); - } - } - $this->dirCreated = true; - } -} \ No newline at end of file diff --git a/websocket/app/Component/MapUpdate.php b/websocket/app/Component/MapUpdate.php deleted file mode 100644 index bc622ab..0000000 --- a/websocket/app/Component/MapUpdate.php +++ /dev/null @@ -1,943 +0,0 @@ - should be overwritten in child instances - * -> is used as "log store" name - */ - const COMPONENT_NAME = 'webSock'; - - /** - * log message unknown task name - */ - const LOG_TEXT_TASK_UNKNOWN = 'unknown task: %s'; - - /** - * log message for denied subscription attempt. -> character data unknown - */ - const LOG_TEXT_SUBSCRIBE_DENY = 'sub. denied for charId: %d'; - - /** - * log message for invalid subscription data - */ - const LOG_TEXT_SUBSCRIBE_INVALID = 'sub. data invalid'; - - /** - * log message for subscribe characterId - */ - const LOG_TEXT_SUBSCRIBE = 'sub. charId: %s to mapIds: [%s]'; - - /** - * log message unsubscribe characterId - */ - const LOG_TEXT_UNSUBSCRIBE = 'unsub. charId: %d from mapIds: [%s]'; - - /** - * log message for map data updated broadcast - */ - const LOG_TEXT_MAP_UPDATE = 'update map data, mapId: %d → broadcast to %d connections'; - - /** - * log message for map subscriptions data updated broadcast - */ - const LOG_TEXT_MAP_SUBSCRIPTIONS = 'update map subscriptions data, mapId: %d. → broadcast to %d connections'; - - /** - * log message for delete mapId broadcast - */ - const LOG_TEXT_MAP_DELETE = 'delete mapId: $d → broadcast to %d connections'; - - /** - * timestamp (ms) from last healthCheck ping - * -> timestamp received from remote TCP socket - * @var int|null - */ - protected $healthCheckToken; - - /** - * expire time for map access tokens (seconds) - * @var int - */ - protected $mapAccessExpireSeconds = 30; - - /** - * character access tokens for clients - * -> tokens are unique and expire onSubscribe! - * [ - * 'charId_1' => [ - * [ - * 'token' => $characterToken1, - * 'expire' => $expireTime1, - * 'characterData' => $characterData1 - * ], - * [ - * 'token' => $characterToken2, - * 'expire' => $expireTime2, - * 'characterData' => $characterData1 - * ] - * ], - * 'charId_2' => [ - * [ - * 'token' => $characterToken3, - * 'expire' => $expireTime3, - * 'characterData' => $characterData2 - * ] - * ] - * ] - * @var array - */ - protected $characterAccessData; - - /** - * access tokens for clients grouped by mapId - * -> tokens are unique and expire onSubscribe! - * @var array - */ - protected $mapAccessData; - - /** - * connected characters - * [ - * 'charId_1' => [ - * '$conn1->resourceId' => $conn1, - * '$conn2->resourceId' => $conn2 - * ], - * 'charId_2' => [ - * '$conn1->resourceId' => $conn1, - * '$conn3->resourceId' => $conn3 - * ] - * ] - * @var array - */ - protected $characters; - - /** - * valid client connections subscribed to maps - * [ - * 'mapId_1' => [ - * 'charId_1' => $charId_1, - * 'charId_2' => $charId_2 - * ], - * 'mapId_2' => [ - * 'charId_1' => $charId_1, - * 'charId_3' => $charId_3 - * ] - * ] - * - * @var array - */ - protected $subscriptions; - - /** - * collection of characterData for valid subscriptions - * [ - * 'charId_1' => $characterData1, - * 'charId_2' => $characterData2 - * ] - * - * @var array - */ - protected $characterData; - - /** - * MapUpdate constructor. - * @param Store $store - */ - public function __construct(Store $store){ - parent::__construct($store); - - $this->characterAccessData = []; - $this->mapAccessData = []; - $this->characters = []; - $this->subscriptions = []; - $this->characterData = []; - } - - /** - * new client connection - * @param ConnectionInterface $conn - */ - public function onOpen(ConnectionInterface $conn){ - parent::onOpen($conn); - } - - /** - * @param ConnectionInterface $conn - */ - public function onClose(ConnectionInterface $conn){ - parent::onClose($conn); - - $this->unSubscribeConnection($conn); - } - - /** - * @param ConnectionInterface $conn - * @param \Exception $e - */ - public function onError(ConnectionInterface $conn, \Exception $e){ - parent::onError($conn, $e); - - // close connection should trigger the onClose() callback for unSubscribe - $conn->close(); - } - - /** - * @param ConnectionInterface $conn - * @param string $msg - */ - public function onMessage(ConnectionInterface $conn, $msg){ - parent::onMessage($conn, $msg); - } - - /** - * @param ConnectionInterface $conn - * @param Payload $payload - */ - protected function dispatchWebSocketPayload(ConnectionInterface $conn, Payload $payload) : void { - switch($payload->task){ - case 'healthCheck': - $this->broadcastHealthCheck($conn, $payload); - break; - case 'subscribe': - $this->subscribe($conn, (array)$payload->load); - break; - case 'unsubscribe': - // make sure characterIds got from client are valid - // -> intersect with subscribed characterIds for current $conn - $characterIds = array_intersect((array)$payload->load, $this->getCharacterIdsByConnection($conn)); - if(!empty($characterIds)){ - $this->unSubscribeCharacterIds($characterIds, $conn); - } - break; - default: - $this->log(['debug', 'error'], $conn, __FUNCTION__, sprintf(static::LOG_TEXT_TASK_UNKNOWN, $payload->task)); - break; - } - } - - /** - * checks healthCheck $token and respond with validation status + subscription stats - * @param ConnectionInterface $conn - * @param Payload $payload - */ - private function broadcastHealthCheck(ConnectionInterface $conn, Payload $payload) : void { - $isValid = $this->validateHealthCheckToken((int)$payload->load); - - $load = [ - 'isValid' => $isValid, - ]; - - // Make sure WebSocket client request is valid - if($isValid){ - // set new healthCheckToken for next check - $load['token'] = $this->setHealthCheckToken(microtime(true)); - - // add subscription stats if $token is valid - $load['subStats'] = $this->getSubscriptionStats(); - } - - $payload->setLoad($load); - - $connections = new \SplObjectStorage(); - $connections->attach($conn); - - $this->broadcast($connections, $payload); - - } - - /** - * compare token (timestamp from initial TCP healthCheck message) with token send from WebSocket - * @param int $token - * @return bool - */ - private function validateHealthCheckToken(int $token) : bool { - $isValid = false; - - if($token && $this->healthCheckToken && $token === (int)$this->healthCheckToken){ - $isValid = true; - } - - // reset token - $this->healthCheckToken = null; - - return $isValid; - } - - /** - * subscribes a connection to valid accessible maps - * @param ConnectionInterface $conn - * @param $subscribeData - */ - private function subscribe(ConnectionInterface $conn, array $subscribeData) : void { - $characterId = (int)$subscribeData['id']; - $characterToken = (string)$subscribeData['token']; - - if($characterId && $characterToken){ - // check if character access token is valid (exists and not expired in $this->characterAccessData) - if($characterData = $this->checkCharacterAccess($characterId, $characterToken)){ - $this->characters[$characterId][$conn->resourceId] = $conn; - - // insert/update characterData cache - // -> even if characterId does not have access to a map "yet" - // -> no maps found but character can get map access at any time later - $this->setCharacterData($characterData); - - // valid character -> check map access - $changedSubscriptionsMapIds = []; - foreach((array)$subscribeData['mapData'] as $data){ - $mapId = (int)$data['id']; - $mapToken = (string)$data['token']; - $mapName = (string)$data['name']; - - if($mapId && $mapToken){ - // check if token is valid (exists and not expired) in $this->mapAccessData - if($this->checkMapAccess($characterId, $mapId, $mapToken)){ - // valid map subscribe request - $this->subscriptions[$mapId]['characterIds'][$characterId] = $characterId; - $this->subscriptions[$mapId]['data']['name'] = $mapName; - $changedSubscriptionsMapIds[] = $mapId; - } - } - } - - sort($changedSubscriptionsMapIds, SORT_NUMERIC); - - $this->log(['debug', 'info'], $conn, __FUNCTION__, - sprintf(static::LOG_TEXT_SUBSCRIBE, $characterId, implode(',', $changedSubscriptionsMapIds)) - ); - - // broadcast all active subscriptions to subscribed connections ------------------------------------------- - $this->broadcastMapSubscriptions($changedSubscriptionsMapIds); - }else{ - $this->log(['debug', 'info'], $conn, __FUNCTION__, sprintf(static::LOG_TEXT_SUBSCRIBE_DENY, $characterId)); - } - }else{ - $this->log(['debug', 'error'], $conn, __FUNCTION__, static::LOG_TEXT_SUBSCRIBE_INVALID); - } - } - - /** - * subscribes an active connection from maps - * @param ConnectionInterface $conn - */ - private function unSubscribeConnection(ConnectionInterface $conn){ - $characterIds = $this->getCharacterIdsByConnection($conn); - $this->unSubscribeCharacterIds($characterIds, $conn); - } - - /** - * unSubscribe a $characterId from ALL maps - * -> if $conn is set -> just unSub the $characterId from this $conn - * @param int $characterId - * @param ConnectionInterface|null $conn - * @return bool - */ - private function unSubscribeCharacterId(int $characterId, ?ConnectionInterface $conn = null) : bool { - if($characterId){ - // unSub from $this->characters --------------------------------------------------------------------------- - if($conn){ - // just unSub a specific connection (e.g. single browser window) - unset($this->characters[$characterId][$conn->resourceId]); - - if( !count($this->characters[$characterId]) ){ - // no connection left for this character - unset($this->characters[$characterId]); - } - // TODO unset $this->>$characterData if $characterId does not have any other map subscribed to - }else{ - // unSub ALL connections from a character (e.g. multiple browsers) - unset($this->characters[$characterId]); - - // unset characterData cache - $this->deleteCharacterData($characterId); - } - - // unSub from $this->subscriptions ------------------------------------------------------------------------ - $changedSubscriptionsMapIds = []; - foreach($this->subscriptions as $mapId => $subData){ - if(array_key_exists($characterId, (array)$subData['characterIds'])){ - unset($this->subscriptions[$mapId]['characterIds'][$characterId]); - - if( !count($this->subscriptions[$mapId]['characterIds']) ){ - // no characters left on this map - unset($this->subscriptions[$mapId]); - } - - $changedSubscriptionsMapIds[] = $mapId; - } - } - - sort($changedSubscriptionsMapIds, SORT_NUMERIC); - - $this->log(['debug', 'info'], $conn, __FUNCTION__, - sprintf(static::LOG_TEXT_UNSUBSCRIBE, $characterId, implode(',', $changedSubscriptionsMapIds)) - ); - - // broadcast all active subscriptions to subscribed connections ------------------------------------------- - $this->broadcastMapSubscriptions($changedSubscriptionsMapIds); - } - - return true; - } - - /** - * unSubscribe $characterIds from ALL maps - * -> if $conn is set -> just unSub the $characterId from this $conn - * @param int[] $characterIds - * @param ConnectionInterface|null $conn - * @return bool - */ - private function unSubscribeCharacterIds(array $characterIds, ?ConnectionInterface $conn = null) : bool { - $response = false; - foreach($characterIds as $characterId){ - $response = $this->unSubscribeCharacterId($characterId, $conn); - } - return $response; - } - - /** - * delete mapId from subscriptions and broadcast "delete msg" to clients - * @param string $task - * @param int $mapId - * @return int - */ - private function deleteMapId(string $task, int $mapId) : int { - $connectionCount = $this->broadcastMapData($task, $mapId, $mapId); - - // remove map from subscriptions - if(isset($this->subscriptions[$mapId])){ - unset($this->subscriptions[$mapId]); - } - - $this->log(['debug', 'info'], null, __FUNCTION__, - sprintf(static::LOG_TEXT_MAP_DELETE, $mapId, $connectionCount) - ); - - return $connectionCount; - } - - /** - * get all mapIds a characterId has subscribed to - * @param int $characterId - * @return int[] - */ - private function getMapIdsByCharacterId(int $characterId) : array { - $mapIds = []; - foreach($this->subscriptions as $mapId => $subData) { - if(array_key_exists($characterId, (array)$subData['characterIds'])){ - $mapIds[] = $mapId; - } - } - return $mapIds; - } - - /** - * @param ConnectionInterface $conn - * @return int[] - */ - private function getCharacterIdsByConnection(ConnectionInterface $conn) : array { - $characterIds = []; - $resourceId = $conn->resourceId; - - foreach($this->characters as $characterId => $resourceIDs){ - if( - array_key_exists($resourceId, $resourceIDs) && - !in_array($characterId, $characterIds) - ){ - $characterIds[] = $characterId; - } - } - return $characterIds; - } - - /** - * @param $mapId - * @return array - */ - private function getCharacterIdsByMapId(int $mapId) : array { - $characterIds = []; - if( - array_key_exists($mapId, $this->subscriptions) && - is_array($this->subscriptions[$mapId]['characterIds']) - ){ - $characterIds = array_keys($this->subscriptions[$mapId]['characterIds']); - } - return $characterIds; - } - - /** - * get connections by $characterIds - * @param int[] $characterIds - * @return \SplObjectStorage - */ - private function getConnectionsByCharacterIds(array $characterIds) : \SplObjectStorage { - $connections = new \SplObjectStorage; - foreach($characterIds as $characterId){ - $connections->addAll($this->getConnectionsByCharacterId($characterId)); - } - return $connections; - } - - /** - * get connections by $characterId - * @param int $characterId - * @return \SplObjectStorage - */ - private function getConnectionsByCharacterId(int $characterId) : \SplObjectStorage { - $connections = new \SplObjectStorage; - if(isset($this->characters[$characterId])){ - foreach(array_keys($this->characters[$characterId]) as $resourceId){ - if( - $this->hasConnectionId($resourceId) && - !$connections->contains($conn = $this->getConnection($resourceId)) - ){ - $connections->attach($conn); - } - } - } - return $connections; - } - - /** - * check character access against $this->characterAccessData whitelist - * @param $characterId - * @param $characterToken - * @return array - */ - private function checkCharacterAccess(int $characterId, string $characterToken) : array { - $characterData = []; - if( !empty($characterAccessData = (array)$this->characterAccessData[$characterId]) ){ - // check expire for $this->characterAccessData -> check ALL characters and remove expired - foreach($characterAccessData as $i => $data){ - $deleteToken = false; - - if( ((int)$data['expire'] - time()) > 0 ){ - // still valid -> check token - if($characterToken === $data['token']){ - $characterData = $data['characterData']; - $deleteToken = true; - // NO break; here -> check other characterAccessData as well - } - }else{ - // token expired - $deleteToken = true; - } - - if($deleteToken){ - unset($this->characterAccessData[$characterId][$i]); - // -> check if tokens for this charId is empty - if( empty($this->characterAccessData[$characterId]) ){ - unset($this->characterAccessData[$characterId]); - - } - } - } - } - - return $characterData; - } - - /** - * check map access against $this->mapAccessData whitelist - * @param $characterId - * @param $mapId - * @param $mapToken - * @return bool - */ - private function checkMapAccess(int $characterId, int $mapId, string $mapToken) : bool { - $access = false; - if( !empty($mapAccessData = (array)$this->mapAccessData[$mapId][$characterId]) ){ - foreach($mapAccessData as $i => $data){ - $deleteToken = false; - // check expire for $this->mapAccessData -> check ALL characters and remove expired - if( ((int)$data['expire'] - time()) > 0 ){ - // still valid -> check token - if($mapToken === $data['token']){ - $access = true; - $deleteToken = true; - } - }else{ - // token expired - $deleteToken = true; - } - - if($deleteToken){ - unset($this->mapAccessData[$mapId][$characterId][$i]); - // -> check if tokens for this charId is empty - if( empty($this->mapAccessData[$mapId][$characterId]) ){ - unset($this->mapAccessData[$mapId][$characterId]); - // -> check if map has no access tokens left for characters - if( empty($this->mapAccessData[$mapId]) ){ - unset($this->mapAccessData[$mapId]); - } - } - } - } - } - return $access; - } - - /** - * broadcast $payload to $connections - * @param \SplObjectStorage $connections - * @param Payload $payload - */ - private function broadcast(\SplObjectStorage $connections, Payload $payload) : void { - $data = json_encode($payload); - foreach($connections as $conn){ - $this->send($conn, $data); - } - } - - // custom calls =================================================================================================== - - /** - * receive data from TCP socket (main App) - * -> send response back - * @param string $task - * @param null|int|array $load - * @return bool|float|int|null - */ - public function receiveData(string $task, $load = null){ - $responseLoad = null; - - switch($task){ - case 'healthCheck': - $responseLoad = $this->setHealthCheckToken((float)$load); - break; - case 'characterUpdate': - $this->updateCharacterData((array)$load); - $mapIds = $this->getMapIdsByCharacterId((int)$load['id']); - $this->broadcastMapSubscriptions($mapIds); - break; - case 'characterLogout': - $responseLoad = $this->unSubscribeCharacterIds((array)$load); - break; - case 'mapConnectionAccess': - $responseLoad = $this->setConnectionAccess($load); - break; - case 'mapAccess': - $responseLoad = $this->setAccess($task, $load); - break; - case 'mapUpdate': - $responseLoad = $this->broadcastMapUpdate($task, (array)$load); - break; - case 'mapDeleted': - $responseLoad = $this->deleteMapId($task, (int)$load); - break; - case 'logData': - $this->handleLogData((array)$load['meta'], (array)$load['log']); - break; - } - - return $responseLoad; - } - - /** - * @param float $token - * @return float - */ - private function setHealthCheckToken(float $token) : float { - $this->healthCheckToken = $token; - return $this->healthCheckToken; - } - - /** - * @param array $characterData - */ - private function setCharacterData(array $characterData) : void { - if($characterId = (int)$characterData['id']){ - $this->characterData[$characterId] = $characterData; - } - } - - /** - * @param int $characterId - * @return array - */ - private function getCharacterData(int $characterId) : array { - return empty($this->characterData[$characterId]) ? [] : $this->characterData[$characterId]; - } - - /** - * @param array $characterIds - * @return array - */ - private function getCharactersData(array $characterIds) : array { - return array_filter($this->characterData, function($characterId) use($characterIds) { - return in_array($characterId, $characterIds); - }, ARRAY_FILTER_USE_KEY); - } - - /** - * @param array $characterData - */ - private function updateCharacterData(array $characterData) : void { - $characterId = (int)$characterData['id']; - if($this->getCharacterData($characterId)){ - $this->setCharacterData($characterData); - } - } - - /** - * @param int $characterId - */ - private function deleteCharacterData(int $characterId) : void { - unset($this->characterData[$characterId]); - } - - /** - * @param array $mapIds - */ - private function broadcastMapSubscriptions(array $mapIds) : void { - $mapIds = array_unique($mapIds); - - foreach($mapIds as $mapId){ - if( - !empty($characterIds = $this->getCharacterIdsByMapId($mapId)) && - !empty($charactersData = $this->getCharactersData($characterIds)) - ){ - $systems = SubscriptionFormatter::groupCharactersDataBySystem($charactersData); - - $mapUserData = (object)[]; - $mapUserData->config = (object)['id' => $mapId]; - $mapUserData->data = (object)['systems' => $systems]; - - $connectionCount = $this->broadcastMapData('mapSubscriptions', $mapId, $mapUserData); - - $this->log(['debug'], null, __FUNCTION__, - sprintf(static::LOG_TEXT_MAP_SUBSCRIPTIONS, $mapId, $connectionCount) - ); - } - } - } - - /** - * @param string $task - * @param array $mapData - * @return int - */ - private function broadcastMapUpdate(string $task, array $mapData) : int { - $mapId = (int)$mapData['config']['id']; - $connectionCount = $this->broadcastMapData($task, $mapId, $mapData); - - $this->log(['debug'], null, __FUNCTION__, - sprintf(static::LOG_TEXT_MAP_UPDATE, $mapId, $connectionCount) - ); - - return $connectionCount; - } - - /** - * send map data to ALL connected clients - * @param string $task - * @param int $mapId - * @param mixed $load - * @return int - */ - private function broadcastMapData(string $task, int $mapId, $load) : int { - $characterIds = $this->getCharacterIdsByMapId($mapId); - $connections = $this->getConnectionsByCharacterIds($characterIds); - - $this->broadcast($connections, $this->newPayload($task, $load, $characterIds)); - - return count($connections); - } - - /** - * set/update map access for allowed characterIds - * @param string $task - * @param array $accessData - * @return int count of connected characters - */ - private function setAccess(string $task, $accessData) : int { - $newMapCharacterIds = []; - - if($mapId = (int)$accessData['id']){ - $mapName = (string)$accessData['name']; - $characterIds = (array)$accessData['characterIds']; - // check all charactersIds that have map access... -------------------------------------------------------- - foreach($characterIds as $characterId){ - // ... for at least ONE active connection ... - // ... and characterData cache exists for characterId - if( - !empty($this->characters[$characterId]) && - !empty($this->getCharacterData($characterId)) - ){ - $newMapCharacterIds[$characterId] = $characterId; - } - } - - $currentMapCharacterIds = (array)$this->subscriptions[$mapId]['characterIds']; - - // broadcast "map delete" to no longer valid characters --------------------------------------------------- - $removedMapCharacterIds = array_keys(array_diff_key($currentMapCharacterIds, $newMapCharacterIds)); - $removedMapCharacterConnections = $this->getConnectionsByCharacterIds($removedMapCharacterIds); - - $this->broadcast($removedMapCharacterConnections, $this->newPayload($task, $mapId, $removedMapCharacterIds)); - - // update map subscriptions ------------------------------------------------------------------------------- - if( !empty($newMapCharacterIds) ){ - // set new characters that have map access (overwrites existing subscriptions for that map) - $this->subscriptions[$mapId]['characterIds'] = $newMapCharacterIds; - $this->subscriptions[$mapId]['data']['name'] = $mapName; - - // check if subscriptions have changed - if( !$this->arraysEqualKeys($currentMapCharacterIds, $newMapCharacterIds) ){ - $this->broadcastMapSubscriptions([$mapId]); - } - }else{ - // no characters (left) on this map - unset($this->subscriptions[$mapId]); - } - } - return count($newMapCharacterIds); - } - - /** - * set map access data (whitelist) tokens for map access - * @param $connectionAccessData - * @return bool - */ - private function setConnectionAccess($connectionAccessData){ - $response = false; - $characterId = (int)$connectionAccessData['id']; - $characterData = $connectionAccessData['characterData']; - $characterToken = $connectionAccessData['token']; - - if( - $characterId && - $characterData && - $characterToken - ){ - // expire time for character and map tokens - $expireTime = time() + $this->mapAccessExpireSeconds; - - // tokens for character access - $this->characterAccessData[$characterId][] = [ - 'token' => $characterToken, - 'expire' => $expireTime, - 'characterData' => $characterData - ]; - - foreach((array)$connectionAccessData['mapData'] as $mapData){ - $mapId = (int)$mapData['id']; - - $this->mapAccessData[$mapId][$characterId][] = [ - 'token' => $mapData['token'], - 'expire' => $expireTime - ]; - } - - $response = 'OK'; - } - - return $response; - } - - /** - * get stats data - * -> lists all channels, subscribed characters + connection info - * @return array - */ - protected function getSubscriptionStats() : array { - $uniqueConnections = []; - $uniqueSubscriptions = []; - $channelsStats = []; - - foreach($this->subscriptions as $mapId => $subData){ - $characterIds = $this->getCharacterIdsByMapId($mapId); - $uniqueMapConnections = []; - - $channelStats = [ - 'channelId' => $mapId, - 'channelName' => $subData['data']['name'], - 'countSub' => count($characterIds), - 'countCon' => 0, - 'subscriptions' => [] - ]; - - foreach($characterIds as $characterId){ - $characterData = $this->getCharacterData($characterId); - $connections = $this->getConnectionsByCharacterId($characterId); - - $characterStats = [ - 'characterId' => $characterId, - 'characterName' => isset($characterData['name']) ? $characterData['name'] : null, - 'countCon' => $connections->count(), - 'connections' => [] - ]; - - foreach($connections as $connection){ - if(!in_array($connection->resourceId, $uniqueMapConnections)){ - $uniqueMapConnections[] = $connection->resourceId; - } - - $metaData = $this->getConnectionData($connection); - $microTime = (float)$metaData['mTimeSend']; - $logTime = Store::getDateTimeFromMicrotime($microTime); - - $characterStats['connections'][] = [ - 'resourceId' => $connection->resourceId, - 'remoteAddress' => $connection->remoteAddress, - 'mTimeSend' => $microTime, - 'mTimeSendFormat1' => $logTime->format('Y-m-d H:i:s.u'), - 'mTimeSendFormat2' => $logTime->format('H:i:s') - ]; - } - - $channelStats['subscriptions'][] = $characterStats; - } - - $uniqueConnections = array_unique(array_merge($uniqueConnections, $uniqueMapConnections)); - $uniqueSubscriptions = array_unique(array_merge($uniqueSubscriptions, $characterIds)); - - $channelStats['countCon'] = count($uniqueMapConnections); - - $channelsStats[] = $channelStats; - } - - return [ - 'countSub' => count($uniqueSubscriptions), - 'countCon' => count($uniqueConnections), - 'channels' => $channelsStats - ]; - } - - /** - * compare two assoc arrays by keys. Key order is ignored - * -> if all keys from array1 exist in array2 && all keys from array2 exist in array 1, arrays are supposed to be equal - * @param array $array1 - * @param array $array2 - * @return bool - */ - protected function arraysEqualKeys(array $array1, array $array2) : bool { - return !array_diff_key($array1, $array2) && !array_diff_key($array2, $array1); - } - - /** - * dispatch log writing to a LogFileHandler - * @param array $meta - * @param array $log - */ - private function handleLogData(array $meta, array $log){ - $logHandler = new LogFileHandler((string)$meta['stream']); - $logHandler->write($log); - } -} \ No newline at end of file diff --git a/websocket/app/Data/Payload.php b/websocket/app/Data/Payload.php deleted file mode 100644 index 5d72db3..0000000 --- a/websocket/app/Data/Payload.php +++ /dev/null @@ -1,94 +0,0 @@ - recipients - * -> e.g if multiple browser tabs are open - * @var null|array - */ - private $characterIds; - - /** - * Payload constructor. - * @param string $task - * @param null $load - * @param array|null $characterIds - */ - public function __construct(string $task, $load = null, ?array $characterIds = null){ - $this->setTask($task); - $this->setLoad($load); - $this->setCharacterIds($characterIds); - } - - /** - * @param string $task - */ - public function setTask(string $task){ - if($task){ - $this->task = $task; - }else{ - throw new \InvalidArgumentException(self::ERROR_TASK_MISSING); - } - } - - /** - * @param null $load - */ - public function setLoad($load = null){ - $this->load = $load; - } - - /** - * @param array|null $characterIds - */ - public function setCharacterIds(?array $characterIds){ - if(is_array($characterIds)){ - $this->characterIds = $characterIds; - }else{ - $this->characterIds = null; - } - } - - /** - * @param $name - * @return mixed - */ - public function __get($name){ - return $this->$name; - } - - /** - * @return array|mixed - */ - public function jsonSerialize(){ - return get_object_vars($this); - } -} \ No newline at end of file diff --git a/websocket/app/Log/ShellColors.php b/websocket/app/Log/ShellColors.php deleted file mode 100644 index 0858179..0000000 --- a/websocket/app/Log/ShellColors.php +++ /dev/null @@ -1,90 +0,0 @@ -foregroundColors['black'] = '0;30'; - $this->foregroundColors['dark_gray'] = '1;30'; - $this->foregroundColors['blue'] = '0;34'; - $this->foregroundColors['light_blue'] = '1;34'; - $this->foregroundColors['green'] = '0;32'; - $this->foregroundColors['light_green'] = '1;32'; - $this->foregroundColors['cyan'] = '0;36'; - $this->foregroundColors['light_cyan'] = '1;36'; - $this->foregroundColors['red'] = '0;31'; - $this->foregroundColors['light_red'] = '1;31'; - $this->foregroundColors['purple'] = '0;35'; - $this->foregroundColors['light_purple'] = '1;35'; - $this->foregroundColors['brown'] = '0;33'; - $this->foregroundColors['yellow'] = '1;33'; - $this->foregroundColors['light_gray'] = '0;37'; - $this->foregroundColors['white'] = '1;37'; - - $this->backgroundColors['black'] = '40'; - $this->backgroundColors['red'] = '41'; - $this->backgroundColors['green'] = '42'; - $this->backgroundColors['yellow'] = '43'; - $this->backgroundColors['blue'] = '44'; - $this->backgroundColors['magenta'] = '45'; - $this->backgroundColors['cyan'] = '46'; - $this->backgroundColors['light_gray'] = '47'; - } - - /** - * get colored string - * @param string $string - * @param string|null $foregroundColor - * @param string|null $backgroundColor - * @return string - */ - public function getColoredString(string $string, ?string $foregroundColor = null, ?string $backgroundColor = null) : string { - $coloredString = ""; - - // Check if given foreground color found - if (isset($this->foregroundColors[$foregroundColor])) { - $coloredString .= "\033[" . $this->foregroundColors[$foregroundColor] . "m"; - } - // Check if given background color found - if (isset($this->backgroundColors[$backgroundColor])) { - $coloredString .= "\033[" . $this->backgroundColors[$backgroundColor] . "m"; - } - - // Add string and end coloring - $coloredString .= $string . "\033[0m"; - - return $coloredString; - } - - /** - * returns all foreground color names - * @return array - */ - public function getForegroundColors() : array { - return array_keys($this->foregroundColors); - } - - /** - * returns all background color names - * @return array - */ - public function getBackgroundColors() : array { - return array_keys($this->backgroundColors); - } -} \ No newline at end of file diff --git a/websocket/app/Log/Store.php b/websocket/app/Log/Store.php deleted file mode 100644 index 28ffbf6..0000000 --- a/websocket/app/Log/Store.php +++ /dev/null @@ -1,220 +0,0 @@ - store size should be limited for memory reasons - * @var array - */ - private $store = []; - - /** - * all valid types for custom log events - * if value is false, logs for this type are ignored - * @var array - */ - protected $logTypes = [ - 'error' => true, - 'info' => true, - 'debug' => true - ]; - - /** - * if Store is locked, current state can not be changed - * @var bool - */ - protected $locked = false; - - /** - * @var ShellColors - */ - static $colors; - - /** - * Store constructor. - * @param string $name - */ - public function __construct(string $name){ - $this->name = $name; - } - - /** - * get all stored log entries - * @return array - */ - public function getStore() : array { - return $this->store; - } - - /** - * @param bool $locked - */ - public function setLocked(bool $locked){ - $this->locked = $locked; - } - - /** - * @return bool - */ - public function isLocked() : bool { - return $this->locked; - } - - /** - * @param int $logLevel - */ - public function setLogLevel(int $logLevel){ - switch($logLevel){ - case 3: - $this->logTypes['error'] = true; - $this->logTypes['info'] = true; - $this->logTypes['debug'] = true; - break; - case 2: - $this->logTypes['error'] = true; - $this->logTypes['info'] = true; - $this->logTypes['debug'] = false; - break; - case 1: - $this->logTypes['error'] = true; - $this->logTypes['info'] = false; - $this->logTypes['debug'] = false; - break; - case 0: - default: - $this->setLocked(true); // no logging - } - } - - /** - * this is used for custom log events like 'error', 'debug',... - * works as dispatcher method that calls individual log*() methods - * @param $logTypes - * @param string|null $remoteAddress - * @param int|null $resourceId - * @param string $action - * @param string $message - */ - public function log($logTypes, ?string $remoteAddress, ?int $resourceId, string $action, string $message = '') : void { - if(!$this->isLocked()){ - // filter out logTypes that should not be logged - $logTypes = array_filter((array)$logTypes, function(string $type) : bool { - return array_key_exists($type, $this->logTypes) && $this->logTypes[$type]; - }); - - if($logTypes){ - // get log entry data - $logData = $this->getLogData($logTypes, $remoteAddress, $resourceId, $action, $message); - - if(self::DEFAULT_LOG_TO_STDOUT){ - $this->echoLog($logData); - } - - // add entry to local store and check size limit for store - $this->store[] = $logData; - $this->store = array_slice($this->store, self::DEFAULT_LOG_STORE_SIZE * -1); - } - } - } - - /** - * get log data as array for a custom log entry - * @param array $logTypes - * @param string|null $remoteAddress - * @param int|null $resourceId - * @param string $action - * @param string $message - * @return array - */ - private function getLogData(array $logTypes, ?string $remoteAddress, ?int $resourceId, string $action, string $message = '') : array { - $file = null; - $lineNum = null; - $function = null; - - $traceIndex = 4; - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $traceIndex); - if(count($backtrace) == $traceIndex){ - $caller = $backtrace[$traceIndex - 2]; - $callerOrig = $backtrace[$traceIndex - 1]; - - $file = substr($caller['file'], strlen(dirname(dirname(dirname($caller['file'])))) + 1); - $lineNum = $caller['line']; - $function = $callerOrig['function']; - } - - $microTime = microtime(true); - $logTime = self::getDateTimeFromMicrotime($microTime); - - return [ - 'store' => $this->name, - 'mTime' => $microTime, - 'mTimeFormat1' => $logTime->format('Y-m-d H:i:s.u'), - 'mTimeFormat2' => $logTime->format('H:i:s'), - 'logTypes' => $logTypes, - 'remoteAddress' => $remoteAddress, - 'resourceId' => $resourceId, - 'fileName' => $file, - 'lineNumber' => $lineNum, - 'function' => $function, - 'action' => $action, - 'message' => $message - ]; - } - - /** - * echo log data to stdout -> terminal - * @param array $logData - */ - private function echoLog(array $logData) : void { - if(!self::$colors){ - self::$colors = new ShellColors(); - } - - $data = [ - self::$colors->getColoredString($logData['mTimeFormat1'], 'dark_gray'), - self::$colors->getColoredString($logData['store'], $logData['store'] == 'webSock' ? 'brown' : 'cyan'), - $logData['remoteAddress'] . ($logData['resourceId'] ? ' #' . $logData['resourceId'] : ''), - self::$colors->getColoredString($logData['fileName'] . ' line ' . $logData['lineNumber'], 'dark_gray'), - self::$colors->getColoredString($logData['function'] . '()' . (($logData['function'] !== $logData['action']) ? ' [' . $logData['action'] . ']' : ''), 'dark_gray'), - implode(',', (array)$logData['logTypes']), - self::$colors->getColoredString($logData['message'], 'light_purple') - ]; - - echo implode(' | ', array_filter($data)) . PHP_EOL; - } - - /** - * @see https://stackoverflow.com/a/29598719/4329969 - * @param float $mTime - * @return \DateTime - */ - public static function getDateTimeFromMicrotime(float $mTime) : \DateTime { - return \DateTime::createFromFormat('U.u', number_format($mTime, 6, '.', '')); - } -} \ No newline at end of file diff --git a/websocket/app/Socket/AbstractSocket.php b/websocket/app/Socket/AbstractSocket.php deleted file mode 100644 index 5753c03..0000000 --- a/websocket/app/Socket/AbstractSocket.php +++ /dev/null @@ -1,67 +0,0 @@ - should be overwritten in child instances - * -> is used as "log store" name - */ - const COMPONENT_NAME = 'default'; - - /** - * global server loop - * @var EventLoop\LoopInterface - */ - protected $loop; - - /** - * @var MessageComponentInterface - */ - protected $handler; - - /** - * @var Store - */ - protected $logStore; - - /** - * AbstractSocket constructor. - * @param EventLoop\LoopInterface $loop - * @param MessageComponentInterface $handler - * @param Store $store - */ - public function __construct( - EventLoop\LoopInterface $loop, - MessageComponentInterface $handler, - Store $store - ){ - $this->loop = $loop; - $this->handler = $handler; - $this->logStore = $store; - - $this->log(['debug', 'info'], null, 'START', 'start Socket server…'); - } - - /** - * @param $logTypes - * @param Socket\ConnectionInterface|null $connection - * @param string $action - * @param string $message - */ - public function log($logTypes, ?Socket\ConnectionInterface $connection, string $action, string $message = '') : void { - if(!$this->logStore->isLocked()){ - $remoteAddress = $connection ? $connection->getRemoteAddress() : null; - $this->logStore->log($logTypes, $remoteAddress, null, $action, $message); - } - } - -} \ No newline at end of file diff --git a/websocket/app/Socket/TcpSocket.php b/websocket/app/Socket/TcpSocket.php deleted file mode 100644 index 0af264c..0000000 --- a/websocket/app/Socket/TcpSocket.php +++ /dev/null @@ -1,551 +0,0 @@ - should be overwritten in child instances - * -> is used as "log store" name - */ - const COMPONENT_NAME = 'tcpSock'; - - /** - * error message for unknown acceptType - * @see TcpSocket::DEFAULT_ACCEPT_TYPE - */ - const ERROR_ACCEPT_TYPE = "Unknown acceptType: '%s'"; - - /** - * error message for connected stream is not readable - */ - const ERROR_STREAM_NOT_READABLE = "Stream is not readable. Remote address: '%s'"; - - /** - * error message for connection stream is not writable - */ - const ERROR_STREAM_NOT_WRITABLE = "Stream is not writable. Remote address: '%s'"; - - /** - * error message for missing 'task' key in payload - */ - const ERROR_TASK_MISSING = "Missing 'task' in payload"; - - /** - * error message for unknown 'task' key in payload - */ - const ERROR_TASK_UNKNOWN = "Unknown 'task': '%s' in payload"; - - /** - * error message for missing method - */ - const ERROR_METHOD_MISSING = "Method '%S' not found"; - - /** - * error message for waitTimeout exceeds - * @see TcpSocket::DEFAULT_WAIT_TIMEOUT - */ - const ERROR_WAIT_TIMEOUT = "Exceeds 'waitTimeout': %ss"; - - /** - * default for: accepted data type - * -> affects en/decoding socket data - */ - const DEFAULT_ACCEPT_TYPE = 'json'; - - /** - * default for: wait timeout - * -> timeout until connection gets closed - * timeout should be "reset" right after successful response send to client - */ - const DEFAULT_WAIT_TIMEOUT = 3.0; - - /** - * default for: send response by end() method (rather than write()) - * -> connection will get closed right after successful response send to client - */ - const DEFAULT_END_WITH_RESPONSE = true; - - /** - * default for: add socket statistic to response payload - */ - const DEFAULT_ADD_STATS = false; - - /** - * max length for JSON data string - * -> throw OverflowException on exceed - */ - const JSON_DECODE_MAX_LENGTH = 65536 * 4; - - /** - * @see TcpSocket::DEFAULT_ACCEPT_TYPE - * @var string - */ - private $acceptType = self::DEFAULT_ACCEPT_TYPE; - - /** - * @see TcpSocket::DEFAULT_WAIT_TIMEOUT - * @var float - */ - private $waitTimeout = self::DEFAULT_WAIT_TIMEOUT; - - /** - * @see TcpSocket::DEFAULT_END_WITH_RESPONSE - * @var bool - */ - private $endWithResponse = self::DEFAULT_END_WITH_RESPONSE; - - /** - * @see TcpSocket::DEFAULT_STATS - * @var bool - */ - private $addStats = self::DEFAULT_ADD_STATS; - - /** - * storage for all active connections - * -> can be used to get current count of connected clients - * @var \SplObjectStorage - */ - private $connections; - - /** - * max count of concurrent open connections - * -> represents number of active connected clients - * @var int - */ - private $maxConnections = 0; - - /** - * timestamp on startup - * @var int - */ - private $startupTime = 0; - - /** - * TcpSocket constructor. - * @param EventLoop\LoopInterface $loop - * @param MessageComponentInterface $handler - * @param Store $store - * @param string $acceptType - * @param float $waitTimeout - * @param bool $endWithResponse - */ - public function __construct( - EventLoop\LoopInterface $loop, - MessageComponentInterface $handler, - Store $store, - string $acceptType = self::DEFAULT_ACCEPT_TYPE, - float $waitTimeout = self::DEFAULT_WAIT_TIMEOUT, - bool $endWithResponse = self::DEFAULT_END_WITH_RESPONSE - ){ - parent::__construct($loop, $handler, $store); - - $this->acceptType = $acceptType; - $this->waitTimeout = $waitTimeout; - $this->endWithResponse = $endWithResponse; - $this->connections = new \SplObjectStorage(); - $this->startupTime = time(); - } - - /** - * @param Socket\ConnectionInterface $connection - */ - public function onConnect(Socket\ConnectionInterface $connection){ - $this->log('debug', $connection, __FUNCTION__, 'open connection…'); - - if($this->isValidConnection($connection)){ - // connection can be used - // add connection to global connection pool - $this->addConnection($connection); - // set waitTimeout timer for connection - $this->setTimerTimeout($connection, $this->waitTimeout); - - // register connection events ... ------------------------------------------------------------------------- - $this->initRead($connection) - ->then($this->initDispatch($connection)) - ->then($this->initResponse($connection)) - ->then( - function(array $payload) use ($connection) { - $this->log(['debug', 'info'], $connection,'DONE', 'task "' . $payload['task'] . '" done → response send'); - }, - function(\Exception $e) use ($connection) { - $this->log(['debug', 'error'], $connection, 'ERROR', $e->getMessage()); - $this->connectionError($connection, $e); - }); - - $connection->on('end', function() use ($connection) { - $this->log('debug', $connection, 'onEnd'); - }); - - $connection->on('close', function() use ($connection) { - $this->log(['debug'], $connection, 'onClose', 'close connection'); - $this->removeConnection($connection); - }); - - $connection->on('error', function(\Exception $e) use ($connection) { - $this->log(['debug', 'error'], $connection, 'onError', $e->getMessage()); - }); - }else{ - // invalid connection -> can not be used - $connection->close(); - } - } - - /** - * @param Socket\ConnectionInterface $connection - * @return Promise\PromiseInterface - */ - protected function initRead(Socket\ConnectionInterface $connection) : Promise\PromiseInterface { - if($connection->isReadable()){ - if('json' == $this->acceptType){ - // new empty stream for processing JSON - $stream = new Stream\ThroughStream(); - $streamDecoded = new NDJson\Decoder($stream, true, 512, 0, self::JSON_DECODE_MAX_LENGTH); - - // promise get resolved on first emit('data') - $promise = Promise\Stream\first($streamDecoded); - - // register on('data') for main input stream - $connection->on('data', function ($chunk) use ($stream) { - // send current data chunk to processing stream -> resolves promise - $stream->emit('data', [$chunk]); - }); - - return $promise; - }else{ - return new Promise\RejectedPromise( - new \InvalidArgumentException( - sprintf(self::ERROR_ACCEPT_TYPE, $this->acceptType) - ) - ); - } - }else{ - return new Promise\RejectedPromise( - new \Exception( - sprintf(self::ERROR_STREAM_NOT_READABLE, $connection->getRemoteAddress()) - ) - ); - } - } - - /** - * init dispatcher for payload - * @param Socket\ConnectionInterface $connection - * @return callable - */ - protected function initDispatch(Socket\ConnectionInterface $connection) : callable { - return function(array $payload) use ($connection) : Promise\PromiseInterface { - $task = (string)$payload['task']; - if(!empty($task)){ - $load = $payload['load']; - $deferred = new Promise\Deferred(); - $this->dispatch($connection, $deferred, $task, $load); - return $deferred->promise(); - }else{ - return new Promise\RejectedPromise( - new \InvalidArgumentException(self::ERROR_TASK_MISSING) - ); - } - }; - } - - /** - * @param Socket\ConnectionInterface $connection - * @param Promise\Deferred $deferred - * @param string $task - * @param null $load - */ - protected function dispatch(Socket\ConnectionInterface $connection, Promise\Deferred $deferred, string $task, $load = null) : void { - $addStatusData = false; - - switch($task){ - case 'getStats': - $addStatusData = true; - $deferred->resolve($this->newPayload($task, null, $addStatusData)); - break; - case 'healthCheck': - $addStatusData = true; - case 'characterUpdate': - case 'characterLogout': - case 'mapConnectionAccess': - case 'mapAccess': - case 'mapUpdate': - case 'mapDeleted': - case 'logData': - if(method_exists($this->handler, 'receiveData')){ - $this->log(['info'], $connection, __FUNCTION__, 'task "' . $task . '" processing…'); - - $deferred->resolve( - $this->newPayload( - $task, - call_user_func_array([$this->handler, 'receiveData'], [$task, $load]), - $addStatusData - ) - ); - }else{ - $deferred->reject(new \Exception(sprintf(self::ERROR_METHOD_MISSING, 'receiveData'))); - } - break; - default: - $deferred->reject(new \InvalidArgumentException(sprintf(self::ERROR_TASK_UNKNOWN, $task))); - } - } - - /** - * @param Socket\ConnectionInterface $connection - * @return callable - */ - protected function initResponse(Socket\ConnectionInterface $connection) : callable { - return function(array $payload) use ($connection) : Promise\PromiseInterface { - $this->log('debug', $connection, 'initResponse', 'task "' . $payload['task'] . '" → init response'); - - $deferred = new Promise\Deferred(); - $this->write($deferred, $connection, $payload); - - return $deferred->promise(); - }; - } - - /** - * @param Promise\Deferred $deferred - * @param Socket\ConnectionInterface $connection - * @param array $payload - */ - protected function write(Promise\Deferred $deferred, Socket\ConnectionInterface $connection, array $payload) : void { - $write = false; - if($connection->isWritable()){ - if('json' == $this->acceptType){ - $connection = new NDJson\Encoder($connection); - } - - // write a new chunk of data to connection stream - $write = $connection->write($payload); - - if($this->endWithResponse){ - // connection should be closed (and removed from this socket server) - $connection->end(); - } - } - - if($write){ - $deferred->resolve($payload); - }else{ - $deferred->reject(new \Exception( - sprintf(self::ERROR_STREAM_NOT_WRITABLE, $connection->getRemoteAddress()) - )); - } - } - - /** - * $connection has error - * -> if writable -> end() connection with $payload (close() is called by default) - * -> if readable -> close() connection - * @param Socket\ConnectionInterface $connection - * @param \Exception $e - */ - protected function connectionError(Socket\ConnectionInterface $connection, \Exception $e){ - $errorMessage = $e->getMessage(); - $this->log(['debug', 'error'], $connection, __FUNCTION__, $errorMessage); - - if($connection->isWritable()){ - if('json' == $this->acceptType){ - $connection = new NDJson\Encoder($connection); - } - - // send "end" data, then close - $connection->end($this->newPayload('error', $errorMessage, true)); - }else{ - // close connection - $connection->close(); - } - } - - /** - * check if $connection is found in global pool - * @param Socket\ConnectionInterface $connection - * @return bool - */ - protected function hasConnection(Socket\ConnectionInterface $connection) : bool { - return $this->connections->contains($connection); - } - - /** - * cancels a previously set timer callback for a $connection - * @param Socket\ConnectionInterface $connection - * @param string $timerName - */ - protected function cancelTimer(Socket\ConnectionInterface $connection, string $timerName){ - if( - $this->hasConnection($connection) && - ($data = (array)$this->connections->offsetGet($connection)) && - isset($data['timers']) && isset($data['timers'][$timerName]) && - ($data['timers'][$timerName] instanceof EventLoop\TimerInterface) - ){ - $this->loop->cancelTimer($data['timers'][$timerName]); - - unset($data['timers'][$timerName]); - $this->connections->offsetSet($connection, $data); - } - } - - /** - * cancels all previously set timers for a $connection - * @param Socket\ConnectionInterface $connection - */ - protected function cancelTimers(Socket\ConnectionInterface $connection){ - if( - $this->hasConnection($connection) && - ($data = (array)$this->connections->offsetGet($connection)) && - isset($data['timers']) - ){ - foreach((array)$data['timers'] as $timerName => $timer){ - $this->loop->cancelTimer($timer); - } - - $data['timers'] = []; - $this->connections->offsetSet($connection, $data); - } - } - - /** - * @param Socket\ConnectionInterface $connection - * @param string $timerName - * @param float $interval - * @param callable $timerCallback - */ - protected function setTimer(Socket\ConnectionInterface $connection, string $timerName, float $interval, callable $timerCallback){ - if( - $this->hasConnection($connection) && - ($data = (array)$this->connections->offsetGet($connection)) && - isset($data['timers']) - ){ - $data['timers'][$timerName] = $this->loop->addTimer($interval, function() use ($connection, $timerCallback) { - $timerCallback($connection); - }); - - // store new timer to $connection - $this->connections->offsetSet($connection, $data); - } - } - - /** - * cancels and removes previous connection timeout timers - * -> set new connection timeout - * @param Socket\ConnectionInterface $connection - * @param float $waitTimeout - */ - protected function setTimerTimeout(Socket\ConnectionInterface $connection, float $waitTimeout = self::DEFAULT_WAIT_TIMEOUT){ - $this->cancelTimer($connection, 'disconnectTimer'); - $this->setTimer($connection, 'disconnectTimer', $waitTimeout, function(Socket\ConnectionInterface $connection) use ($waitTimeout) { - $errorMessage = sprintf(self::ERROR_WAIT_TIMEOUT, $waitTimeout); - - $this->connectionError( - $connection, - new Promise\Timer\TimeoutException($waitTimeout, $errorMessage) - ); - }); - } - - /** - * add new connection to global pool - * @param Socket\ConnectionInterface $connection - */ - protected function addConnection(Socket\ConnectionInterface $connection){ - if(!$this->hasConnection($connection)){ - $this->connections->attach($connection, [ - 'remoteAddress' => $connection->getRemoteAddress(), - 'timers' => [] - ]); - - // update maxConnections count - $this->maxConnections = max($this->connections->count(), $this->maxConnections); - - $this->log(['debug'], $connection, __FUNCTION__, 'add new connection'); - }else{ - $this->log(['debug'], $connection, __FUNCTION__, 'connection already exists'); - } - } - - /** - * remove $connection from global connection pool - * @param Socket\ConnectionInterface $connection - */ - protected function removeConnection(Socket\ConnectionInterface $connection){ - if($this->hasConnection($connection)){ - $this->log(['debug'], $connection, __FUNCTION__, 'remove connection'); - $this->cancelTimers($connection); - $this->connections->detach($connection); - } - } - - /** - * get new payload - * @param string $task - * @param null $load - * @param bool $addStats - * @return array - */ - protected function newPayload(string $task, $load = null, bool $addStats = false) : array { - $payload = [ - 'task' => $task, - 'load' => $load - ]; - - if($addStats || $this->addStats){ - // add socket statistics - $payload['stats'] = $this->getStats(); - } - - return $payload; - } - - /** - * check if connection is "valid" and can be used for data transfer - * @param Socket\ConnectionInterface $connection - * @return bool - */ - protected function isValidConnection(Socket\ConnectionInterface $connection) : bool { - return $connection->isReadable() || $connection->isWritable(); - } - - /** - * get socket server statistics - * -> e.g. connected clients count - * @return array - */ - protected function getStats() : array { - return [ - 'tcpSocket' => $this->getSocketStats(), - 'webSocket' => $this->handler->getSocketStats() - ]; - } - - /** - * get TcpSocket stats data - * @return array - */ - protected function getSocketStats() : array { - return [ - 'startup' => time() - $this->startupTime, - 'connections' => $this->connections->count(), - 'maxConnections' => $this->maxConnections, - 'logs' => array_reverse($this->logStore->getStore()) - ]; - } -} \ No newline at end of file diff --git a/websocket/app/WebSockets.php b/websocket/app/WebSockets.php deleted file mode 100644 index acc9730..0000000 --- a/websocket/app/WebSockets.php +++ /dev/null @@ -1,113 +0,0 @@ -dsn = $dsn; - $this->wsListenPort = $wsListenPort; - $this->wsListenHost = $wsListenHost; - $this->debug = $debug; - - $this->startMapSocket(); - } - - private function startMapSocket(){ - // global EventLoop - $loop = EventLoop\Factory::create(); - - // new Stores for logging ------------------------------------------------------------------------------------- - $webSocketLogStore = new Store(Component\MapUpdate::COMPONENT_NAME); - $webSocketLogStore->setLogLevel($this->debug); - - $tcpSocketLogStore = new Store(TcpSocket::COMPONENT_NAME); - $tcpSocketLogStore->setLogLevel($this->debug); - - // global MessageComponent (main app) (handles all business logic) -------------------------------------------- - $mapUpdate = new Component\MapUpdate($webSocketLogStore); - - $loop->addPeriodicTimer(3, function(EventLoop\TimerInterface $timer) use ($mapUpdate) { - $mapUpdate->housekeeping($timer); - }); - - // TCP Socket ------------------------------------------------------------------------------------------------- - $tcpSocket = new TcpSocket($loop, $mapUpdate, $tcpSocketLogStore); - // TCP Server (WebServer <-> TCPServer <-> TCPSocket communication) - $server = new Socket\Server($this->dsn, $loop, [ - 'tcp' => [ - 'backlog' => 20, - 'so_reuseport' => true - ] - ]); - - $server->on('connection', function(Socket\ConnectionInterface $connection) use ($tcpSocket) { - $tcpSocket->onConnect($connection); - }); - - $server->on('error', function(\Exception $e) use ($tcpSocket) { - $tcpSocket->log(['debug', 'error'], null, 'onError', $e->getMessage()); - }); - - // WebSocketServer -------------------------------------------------------------------------------------------- - - // Binding to 0.0.0.0 means remotes can connect (Web Clients) - $webSocketURI = $this->wsListenHost . ':' . $this->wsListenPort; - - // Set up our WebSocket server for clients subscriptions - $webSock = new Socket\TcpServer($webSocketURI, $loop); - new IoServer( - new HttpServer( - new WsServer( - $mapUpdate - ) - ), - $webSock - ); - - $loop->run(); - } - -} \ No newline at end of file diff --git a/websocket/cmd.php b/websocket/cmd.php deleted file mode 100644 index a4ebb32..0000000 --- a/websocket/cmd.php +++ /dev/null @@ -1,91 +0,0 @@ - default values - // The default values should be fine for 99% of you! - $longOpts = [ - 'wsHost:' => '0.0.0.0', // WebSocket connection (for WebClients => Browser). '0.0.0.0' <-- any client can connect! - 'wsPort:' => 8020, // ↪ default WebSocket URI: 127.0.0.1:8020. This is where Nginx must proxy WebSocket traffic to - 'tcpHost:' => '127.0.0.1', // TcpSocket connection (for WebServer ⇄ WebSocket) - 'tcpPort:' => 5555, // ↪ default TcpSocket URI: tcp://127.0.0.1:5555 - 'debug:' => 2 // Debug level [0-3] 0 = silent, 1 = errors, 2 = error + info, 3 = error + info + debug - ]; - - // get options from CLI parameter + default values - $cliOpts = getopt('', array_keys($longOpts)); - - $options = []; - array_walk($longOpts, function($defaultVal, $optKey) use ($cliOpts, &$options) { - $key = trim($optKey, ':'); - $val = $defaultVal; - if(array_key_exists($key, $cliOpts)){ - $val = is_int($defaultVal) ? (int)$cliOpts[$key] : $cliOpts[$key] ; - } - $options[$key] = $val; - }); - - /** - * print current config parameters to Shell - * @param array $longOpts - * @param array $options - */ - $showHelp = function(array $longOpts, array $options){ - $optKeys = array_keys($longOpts); - $colors = new Socket\Log\ShellColors(); - $data = []; - - // headline for CLI config parameters - $rowData = $colors->getColoredString(str_pad(' param', 12), 'white'); - $rowData .= $colors->getColoredString(str_pad('value', 18, ' ', STR_PAD_LEFT), 'white'); - $rowData .= $colors->getColoredString(str_pad('default', 15, ' ', STR_PAD_LEFT), 'white'); - - $data[] = $rowData; - $data[] = str_pad(' ', 45, '-'); - - $i = 0; - foreach($options as $optKey => $optVal){ - $rowData = $colors->getColoredString(str_pad(' -' . $optKey, 12), 'yellow'); - $rowData .= $colors->getColoredString(str_pad($optVal, 18, ' ', STR_PAD_LEFT), 'light_purple'); - $rowData .= $colors->getColoredString(str_pad($longOpts[$optKeys[$i]], 15, ' ', STR_PAD_LEFT), 'dark_gray'); - $data[] = $rowData; - $i++; - } - $data[] = ''; - - echo implode(PHP_EOL, $data) . PHP_EOL; - }; - - /** - * set error reporting based on debug option value - * @param int $debug - */ - $setErrorReporting = function(int $debug){ - switch($debug){ - case 0: error_reporting(0); break; // Turn off all error reporting - case 1: error_reporting(E_ERROR); break; // Errors only - case 2: error_reporting(E_ALL & ~E_NOTICE); break; // Report all errors except E_NOTICE - default: error_reporting(E_ALL); - } - }; - - $setErrorReporting($options['debug']); - - if($options['debug']){ - // print if -debug > 0 - $showHelp($longOpts, $options); - } - - $dsn = 'tcp://' . $options['tcpHost'] . ':' . $options['tcpPort']; - - new Socket\WebSockets($dsn, $options['wsPort'], $options['wsHost'], $options['debug']); - -}else{ - echo "Script need to be called by CLI!"; -} - - - diff --git a/websocket/composer.json b/websocket/composer.json deleted file mode 100644 index da4cf83..0000000 --- a/websocket/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "exodus4d/pathfinder_websocket", - "description": "WebSocket extension for 'Pathfinder'", - "minimum-stability": "stable", - "license": "MIT", - "authors": [ - { - "name": "Mark Friedrich", - "email": "pathfinder@exodus4d.de" - } - ], - "autoload": { - "psr-4": { - "Exodus4D\\Socket\\": "app/" - } - }, - "require": { - "php-64bit": ">=7.1", - "ext-json": "*", - "cboden/ratchet": "0.4.x", - "react/promise-stream": "1.2.*", - "clue/ndjson-react": "1.1.*" - }, - "suggest": { - "ext-event": "If installed, 'ExtEventLoop' class will get used as default event loop. Better performance. https://pecl.php.net/package/event" - } -} \ No newline at end of file diff --git a/websocket/composer.lock b/websocket/composer.lock deleted file mode 100644 index 712c634..0000000 --- a/websocket/composer.lock +++ /dev/null @@ -1,1120 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "3ab6b63f00cc99da3ef0ca2500f96d9b", - "packages": [ - { - "name": "cboden/ratchet", - "version": "v0.4.2", - "source": { - "type": "git", - "url": "https://github.com/ratchetphp/Ratchet.git", - "reference": "57721e1f184f9e29378fc5363867c47ddda743fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/57721e1f184f9e29378fc5363867c47ddda743fd", - "reference": "57721e1f184f9e29378fc5363867c47ddda743fd", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^1.0", - "php": ">=5.4.2", - "ratchet/rfc6455": "^0.2", - "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", - "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0", - "symfony/routing": "^2.6|^3.0|^4.0|^5.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ratchet\\": "src/Ratchet" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "role": "Developer" - } - ], - "description": "PHP WebSocket library", - "homepage": "http://socketo.me", - "keywords": [ - "Ratchet", - "WebSockets", - "server", - "sockets", - "websocket" - ], - "time": "2020-01-27T23:08:40+00:00" - }, - { - "name": "clue/ndjson-react", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "767ec9543945802b5766fab0da4520bf20626f66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/767ec9543945802b5766fab0da4520bf20626f66", - "reference": "767ec9543945802b5766fab0da4520bf20626f66", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/stream": "^1.0 || ^0.7 || ^0.6" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.0 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "time": "2020-02-04T11:48:52+00:00" - }, - { - "name": "evenement/evenement", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Evenement": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "time": "2017-07-23T21:35:13+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" - }, - "suggest": { - "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Schultze", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2019-07-01T23:21:34+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "ratchet/rfc6455", - "version": "v0.2.6", - "source": { - "type": "git", - "url": "https://github.com/ratchetphp/RFC6455.git", - "reference": "879e48c840f8dbc296d68d6a5030673df79bd916" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/879e48c840f8dbc296d68d6a5030673df79bd916", - "reference": "879e48c840f8dbc296d68d6a5030673df79bd916", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^1.0", - "php": ">=5.4.2" - }, - "require-dev": { - "phpunit/phpunit": "4.8.*", - "react/socket": "^1.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ratchet\\RFC6455\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "role": "Developer" - } - ], - "description": "RFC6455 WebSocket protocol handler", - "homepage": "http://socketo.me", - "keywords": [ - "WebSockets", - "rfc6455", - "websocket" - ], - "time": "2019-12-15T10:18:18+00:00" - }, - { - "name": "react/cache", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/aa10d63a1b40a36a486bdf527f28bac607ee6466", - "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "~2.0|~1.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "time": "2019-07-11T13:45:28+00:00" - }, - { - "name": "react/dns", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "a214d90c2884dac18d0cac6176202f247b66d762" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/a214d90c2884dac18d0cac6176202f247b66d762", - "reference": "a214d90c2884dac18d0cac6176202f247b66d762", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.0 || ^0.5", - "react/promise": "^2.7 || ^1.2.1", - "react/promise-timer": "^1.2" - }, - "require-dev": { - "clue/block-react": "^1.2", - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "time": "2019-08-15T09:06:31+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "6d24de090cd59cfc830263cfba965be77b563c13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6d24de090cd59cfc830263cfba965be77b563c13", - "reference": "6d24de090cd59cfc830263cfba965be77b563c13", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" - }, - "suggest": { - "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop", - "ext-uv": "* for ExtUvLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "time": "2020-01-01T18:39:52+00:00" - }, - { - "name": "react/promise", - "version": "v2.7.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "time": "2019-01-07T21:25:54+00:00" - }, - { - "name": "react/promise-stream", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise-stream.git", - "reference": "6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise-stream/zipball/6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe", - "reference": "6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/promise": "^2.1 || ^1.2", - "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6" - }, - "require-dev": { - "clue/block-react": "^1.0", - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", - "react/promise-timer": "^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Promise\\Stream\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@lueck.tv" - } - ], - "description": "The missing link between Promise-land and Stream-land for ReactPHP", - "homepage": "https://github.com/reactphp/promise-stream", - "keywords": [ - "Buffer", - "async", - "promise", - "reactphp", - "stream", - "unwrap" - ], - "time": "2019-07-03T12:29:10+00:00" - }, - { - "name": "react/promise-timer", - "version": "v1.5.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise-timer.git", - "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/35fb910604fd86b00023fc5cda477c8074ad0abc", - "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", - "react/promise": "^2.7.0 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Promise\\Timer\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@lueck.tv" - } - ], - "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", - "homepage": "https://github.com/reactphp/promise-timer", - "keywords": [ - "async", - "event-loop", - "promise", - "reactphp", - "timeout", - "timer" - ], - "time": "2019-03-27T18:10:32+00:00" - }, - { - "name": "react/socket", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "97522e24987365e1ed873f0f4884900747a668e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/97522e24987365e1ed873f0f4884900747a668e0", - "reference": "97522e24987365e1ed873f0f4884900747a668e0", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.1", - "react/event-loop": "^1.0 || ^0.5", - "react/promise": "^2.6.0 || ^1.2.1", - "react/promise-timer": "^1.4.0", - "react/stream": "^1.1" - }, - "require-dev": { - "clue/block-react": "^1.2", - "phpunit/phpunit": "^7.5 || ^6.4 || ^5.7 || ^4.8.35", - "react/promise-stream": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "time": "2020-03-12T12:15:14+00:00" - }, - { - "name": "react/stream", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/50426855f7a77ddf43b9266c22320df5bf6c6ce6", - "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "time": "2019-01-01T16:15:09+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v5.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", - "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", - "shasum": "" - }, - "require": { - "php": "^7.2.5", - "symfony/mime": "^4.4|^5.0", - "symfony/polyfill-mbstring": "~1.1" - }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "^4.4|^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony HttpFoundation Component", - "homepage": "https://symfony.com", - "time": "2020-02-14T07:43:07+00:00" - }, - { - "name": "symfony/mime", - "version": "v5.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c", - "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c", - "shasum": "" - }, - "require": { - "php": "^7.2.5", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "symfony/mailer": "<4.4" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10", - "symfony/dependency-injection": "^4.4|^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A library to manipulate MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "time": "2020-02-04T09:41:09+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.14.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6842f1a39cf7d580655688069a03dd7cd83d244a", - "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.14-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "time": "2020-01-17T12:01:36+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.14.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.14-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2020-01-13T11:15:53+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.14.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", - "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.14-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2020-01-13T11:15:53+00:00" - }, - { - "name": "symfony/routing", - "version": "v5.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "d6ca39fd05c1902bf34d724ba06fb8044a0b46de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d6ca39fd05c1902bf34d724ba06fb8044a0b46de", - "reference": "d6ca39fd05c1902bf34d724ba06fb8044a0b46de", - "shasum": "" - }, - "require": { - "php": "^7.2.5" - }, - "conflict": { - "symfony/config": "<5.0", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "psr/log": "~1.0", - "symfony/config": "^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation loader", - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Routing\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Routing Component", - "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], - "time": "2020-02-25T14:24:11+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php-64bit": ">=7.1", - "ext-json": "*" - }, - "platform-dev": [] -}