diff --git a/Dockerfile b/Dockerfile index 4b51c36..67ab540 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,19 @@ -FROM composer:1.6 as composer +FROM composer AS composer COPY . /app RUN composer install --no-interaction --no-dev --ignore-platform-reqs --optimize-autoloader -FROM php:8.1-cli -MAINTAINER Xibo Signage Ltd +FROM php:8.2-cli +LABEL org.opencontainers.image.authors="Xibo Signage Ltd " -ENV XMR_DEBUG false -ENV XMR_QUEUE_POLL 5 -ENV XMR_QUEUE_SIZE 10 -ENV XMR_IPV6RESPSUPPORT false -ENV XMR_IPV6PUBSUPPORT false +ENV XMR_DEBUG=false +ENV XMR_QUEUE_POLL=5 +ENV XMR_QUEUE_SIZE=10 +ENV XMR_IPV6PUBSUPPORT=false +ENV XMR_RELAY_OLD_MESSAGES=false +ENV XMR_RELAY_MESSAGES=false +ENV XMR_SOCKETS_WS=0.0.0.0:8080 +ENV XMR_SOCKETS_API=0.0.0.0:8081 +ENV XMR_SOCKETS_ZM_PORT=9505 RUN apt-get update && apt-get install -y libzmq3-dev git \ && rm -rf /var/lib/apt/lists/* @@ -24,7 +28,7 @@ RUN git clone https://github.com/zeromq/php-zmq.git \ RUN docker-php-ext-enable zmq -EXPOSE 9505 50001 +EXPOSE 8080 8081 9505 COPY ./entrypoint.sh /entrypoint.sh COPY . /opt/xmr diff --git a/composer.json b/composer.json index 347b9b9..967997a 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,22 @@ "bin": ["bin/xmr.phar"], "config": { "platform": { - "php": "8.1", + "php": "8.2", "ext-zmq": "1" } }, "require": { - "php": ">=8.1", + "php": ">=8.2", "monolog/monolog": "^1.17", - "react/zmq": "^0.4.0" + "react/react": "^1.4", + "react/socket": "^1.16", + "react/zmq": "^0.4.0", + "cboden/ratchet": "^0.4.4", + "guzzlehttp/guzzle": "^7.9" + }, + "autoload": { + "psr-4": { + "Xibo\\": "src/" + } } } diff --git a/composer.lock b/composer.lock index c3fe608..3b65780 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,95 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "be10c4aceb6ae6e6de027b6f5dfc5028", + "content-hash": "58cf90f353cd3f8d2862b0ebb1dff300", "packages": [ + { + "name": "cboden/ratchet", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "5012dc954541b40c5599d286fd40653f5716a38f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/5012dc954541b40c5599d286fd40653f5716a38f", + "reference": "5012dc954541b40c5599d286fd40653f5716a38f", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.7|^2.0", + "php": ">=5.4.2", + "ratchet/rfc6455": "^0.3.1", + "react/event-loop": ">=0.4", + "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", + "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0", + "symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.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" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/Ratchet/issues", + "source": "https://github.com/ratchetphp/Ratchet/tree/v0.4.4" + }, + "time": "2021-12-14T00:20:41+00:00" + }, { "name": "evenement/evenement", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/igorw/evenement.git", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { - "psr-0": { - "Evenement": "src" + "psr-4": { + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -47,58 +110,119 @@ "event-dispatcher", "event-emitter" ], - "time": "2017-07-23T21:35:13+00:00" + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "monolog/monolog", - "version": "1.27.0", + "name": "fig/http-message-util", + "version": "1.1.5", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "52ebd235c1f7e0d5e1b16464b695a28335f8e44a" + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/52ebd235c1f7e0d5e1b16464b695a28335f8e44a", - "reference": "52ebd235c1f7e0d5e1b16464b695a28335f8e44a", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/http-client-implementation": "1.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Monolog\\": "src/Monolog" + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -107,56 +231,104 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "description": "Guzzle is a PHP HTTP client library", "keywords": [ - "log", - "logging", - "psr-3" + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, "funding": [ { - "url": "https://github.com/Seldaek", + "url": "https://github.com/GrahamCampbell", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", "type": "tidelift" } ], - "time": "2022-03-13T20:29:46+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "guzzlehttp/promises", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -165,48 +337,92 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Guzzle promises library", "keywords": [ - "log", - "psr", - "psr-3" + "promise" ], - "time": "2021-05-03T11:20:27+00:00" + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-10-17T10:06:22+00:00" }, { - "name": "react/event-loop", - "version": "v1.2.0", + "name": "guzzlehttp/psr7", + "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "be6dee480fc4692cec0504e65eb486e3be1aa6f2" + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/be6dee480fc4692cec0504e65eb486e3be1aa6f2", - "reference": "be6dee480fc4692cec0504e65eb486e3be1aa6f2", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { - "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop", - "ext-uv": "* for ExtUvLoop" + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { "psr-4": { - "React\\EventLoop\\": "src" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -215,83 +431,1734 @@ ], "authors": [ { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" }, { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" }, { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ - "asynchronous", - "event-loop" + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, "funding": [ { - "url": "https://github.com/WyriHaximus", + "url": "https://github.com/GrahamCampbell", "type": "github" }, { - "url": "https://github.com/clue", + "url": "https://github.com/Nyholm", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" } ], - "time": "2021-07-11T12:31:24+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { - "name": "react/zmq", - "version": "v0.4.0", + "name": "monolog/monolog", + "version": "1.27.1", "source": { "type": "git", - "url": "https://github.com/friends-of-reactphp/zmq.git", - "reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39" + "url": "https://github.com/Seldaek/monolog.git", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/friends-of-reactphp/zmq/zipball/13dec0bd2397adcc5d6aa54c8d7f0982fba66f39", - "reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", "shasum": "" }, "require": { - "evenement/evenement": "^3.0 || ^2.0", - "ext-zmq": "*", - "php": ">=5.4.0", - "react/event-loop": "^1.0 || ^0.5 || ^0.4" + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4" + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "~4.5", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" }, "type": "library", "autoload": { "psr-4": { - "React\\ZMQ\\": "src" + "Monolog\\": "src/Monolog" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "ZeroMQ bindings for React.", - "keywords": [ - "zeromq", - "zmq" + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } ], - "time": "2018-05-18T15:27:55+00:00" + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-06-09T08:53:42+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.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": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.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" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+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.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ratchet/rfc6455", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2 || ^1.7", + "php": ">=5.4.2" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "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" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" + }, + "time": "2021-12-09T23:20:49+00:00" + }, + { + "name": "react/async", + "version": "v4.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/async.git", + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5", + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.8 || ^1.2.1" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39", + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Async\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async utilities and fibers for ReactPHP", + "keywords": [ + "async", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/async/issues", + "source": "https://github.com/reactphp/async/tree/v4.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-04T14:40:02+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/http", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/http.git", + "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/http/zipball/8db02de41dcca82037367f67a2d4be365b1c4db9", + "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "fig/http-message-util": "^1.1", + "php": ">=5.3.0", + "psr/http-message": "^1.0", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.3 || ^1.2.1", + "react/socket": "^1.16", + "react/stream": "^1.4" + }, + "require-dev": { + "clue/http-proxy-react": "^1.8", + "clue/reactphp-ssh-proxy": "^1.4", + "clue/socks-react": "^1.4", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.2 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP", + "keywords": [ + "async", + "client", + "event-driven", + "http", + "http client", + "http server", + "https", + "psr-7", + "reactphp", + "server", + "streaming" + ], + "support": { + "issues": "https://github.com/reactphp/http/issues", + "source": "https://github.com/reactphp/http/tree/v1.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-11-20T15:24:08+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/promise-stream", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-stream.git", + "reference": "5c7ec3450f558deb779742e33967d837e2db7871" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-stream/zipball/5c7ec3450f558deb779742e33967d837e2db7871", + "reference": "5c7ec3450f558deb779742e33967d837e2db7871", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/promise": "^3 || ^2.1 || ^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "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" + ], + "support": { + "issues": "https://github.com/reactphp/promise-stream/issues", + "source": "https://github.com/reactphp/promise-stream/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-12-13T11:32:02+00:00" + }, + { + "name": "react/promise-timer", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7.0 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "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" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-04T14:27:45+00:00" + }, + { + "name": "react/react", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/reactphp.git", + "reference": "726e5de40567c9effaa8e5665b1a2621af8d7ee9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/reactphp/zipball/726e5de40567c9effaa8e5665b1a2621af8d7ee9", + "reference": "726e5de40567c9effaa8e5665b1a2621af8d7ee9", + "shasum": "" + }, + "require": { + "php": ">=5.3.8", + "react/async": "^4 || ^3 || ^2", + "react/cache": "^1.1", + "react/dns": "^1.11", + "react/event-loop": "^1.4", + "react/http": "^1.8", + "react/promise": "^3 || ^2.10 || ^1.2", + "react/promise-stream": "^1.6", + "react/promise-timer": "^1.9", + "react/socket": "^1.13", + "react/stream": "^1.3" + }, + "require-dev": { + "clue/stream-filter": "^1.3", + "phpunit/phpunit": "^9.6 || ^7.5 || ^5.7 || ^4.8.36", + "react/async": "^4.2@dev || ^3.2@dev || ^4 || ^3 || ^2", + "react/dns": "^1.12@dev", + "react/http": "^1.10@dev", + "react/promise": "^3@dev || ^2.10 || ^1.2", + "react/promise-stream": "^1.7@dev", + "react/promise-timer": "^1.10@dev", + "react/socket": "^1.14@dev" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "ReactPHP: Event-driven, non-blocking I/O with PHP.", + "homepage": "https://reactphp.org/", + "keywords": [ + "asynchronous", + "reactor", + "reactphp" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/reactphp/reactphp/issues", + "source": "https://github.com/reactphp/reactphp/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-07-11T16:08:54+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "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" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "react/zmq", + "version": "v0.4.0", + "source": { + "type": "git", + "url": "https://github.com/friends-of-reactphp/zmq.git", + "reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/friends-of-reactphp/zmq/zipball/13dec0bd2397adcc5d6aa54c8d7f0982fba66f39", + "reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0", + "ext-zmq": "*", + "php": ">=5.4.0", + "react/event-loop": "^1.0 || ^0.5 || ^0.4" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ZMQ\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "ZeroMQ bindings for React.", + "keywords": [ + "zeromq", + "zmq" + ], + "support": { + "issues": "https://github.com/friends-of-reactphp/zmq/issues", + "source": "https://github.com/friends-of-reactphp/zmq/tree/master" + }, + "time": "2018-05-18T15:27:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.4.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T18:58:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.4.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T15:31:34+00:00" } ], "packages-dev": [], @@ -301,12 +2168,12 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.1" + "php": ">=8.2" }, "platform-dev": [], "platform-overrides": { - "php": "8.1", + "php": "8.2", "ext-zmq": "1" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 29589cc..74ec577 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,9 @@ version: "3" services: xmr: build: . + ports: + - "8080:8080" + - "8081:8081" environment: XMR_DEBUG: "true" volumes: diff --git a/entrypoint.sh b/entrypoint.sh index 039ab5a..23db7b2 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,14 +1,39 @@ #!/bin/sh +# +# Copyright (C) 2025 Xibo Signage Ltd +# +# Xibo - Digital Signage - https://xibosignage.com +# +# This file is part of Xibo. +# +# Xibo is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# Xibo is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Xibo. If not, see . +# + # Write config.json echo '{' > /opt/xmr/config.json -echo ' "listenOn": "tcp://*:50001",' >> /opt/xmr/config.json -echo ' "pubOn": ["tcp://*:9505"],' >> /opt/xmr/config.json +echo ' "sockets": {' >> /opt/xmr/config.json +echo ' "ws": "'$XMR_SOCKETS_WS'",' >> /opt/xmr/config.json +echo ' "api": "'$XMR_SOCKETS_API'",' >> /opt/xmr/config.json +echo ' "zmq": ["tcp://*:'$XMR_SOCKETS_ZM_PORT'"]' >> /opt/xmr/config.json +echo ' },' >> /opt/xmr/config.json echo ' "queuePoll": '$XMR_QUEUE_POLL',' >> /opt/xmr/config.json echo ' "queueSize": '$XMR_QUEUE_SIZE',' >> /opt/xmr/config.json echo ' "debug": '$XMR_DEBUG',' >> /opt/xmr/config.json -echo ' "ipv6RespSupport": '$XMR_IPV6RESPSUPPORT',' >> /opt/xmr/config.json -echo ' "ipv6PubSupport": '$XMR_IPV6PUBSUPPORT >> /opt/xmr/config.json +echo ' "ipv6PubSupport": '$XMR_IPV6PUBSUPPORT',' >> /opt/xmr/config.json +echo ' "relayOldMessages": "'$XMR_RELAY_OLD_MESSAGES'",' >> /opt/xmr/config.json +echo ' "relayMessages": "'$XMR_RELAY_MESSAGES'"' >> /opt/xmr/config.json echo '}' >> /opt/xmr/config.json /usr/local/bin/php /opt/xmr/index.php \ No newline at end of file diff --git a/index.php b/index.php index 81a120a..47a81a9 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,9 @@ #!/usr/bin/env php . - * -sequenceDiagram -Player->> CMS: Register -Note right of Player: Register contains the XMR Channel -CMS->> XMR: PlayerAction -XMR->> CMS: ACK -XMR-->> Player: PlayerAction - * */ + +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Ratchet\Http\HttpServer; +use Ratchet\Server\IoServer; +use Ratchet\WebSocket\WsServer; +use React\EventLoop\Loop; +use React\Http\Message\Response; +use Xibo\Controller\Api; +use Xibo\Controller\Relay; +use Xibo\Controller\Server; +use Xibo\Entity\Queue; + require 'vendor/autoload.php'; -function exception_error_handler($severity, $message, $file, $line) { +// TODO: ratchet does not support PHP8 +error_reporting(E_ALL ^ E_DEPRECATED); +ini_set('display_errors', 0); + +set_error_handler(function($severity, $message, $file, $line) { if (!(error_reporting() & $severity)) { // This error code is not included in error_reporting return; } throw new ErrorException($message, 0, $severity, $file, $line); -} -set_error_handler("exception_error_handler"); +}); // Decide where to look for the config file $dirname = (Phar::running(false) == '') ? __DIR__ : dirname(Phar::running(false)); $config = $dirname . '/config.json'; -if (!file_exists($config)) +if (!file_exists($config)) { throw new InvalidArgumentException('Missing ' . $config . ' file, please create one in ' . $dirname); +} $configString = file_get_contents($config); $config = json_decode($configString); -if ($config === null) +if ($config === null) { throw new InvalidArgumentException('Cannot decode config file ' . json_last_error_msg() . ' config string is [' . $configString . ']'); +} -if ($config->debug) - $logLevel = \Monolog\Logger::DEBUG; -else - $logLevel = \Monolog\Logger::WARNING; - -// Queue settings -$queuePoll = (property_exists($config, 'queuePoll')) ? $config->queuePoll : 5; -$queueSize = (property_exists($config, 'queueSize')) ? $config->queueSize : 10; +$logLevel = $config->debug ? Logger::DEBUG : Logger::WARNING; // Set up logging to file -$log = new \Monolog\Logger('xmr'); -$log->pushHandler(new \Monolog\Handler\StreamHandler(STDOUT, $logLevel)); -$log->info(sprintf('Starting up - listening for CMS on %s.', $config->listenOn)); - -try { - $loop = \React\EventLoop\Factory::create(); - - /** - * ZMQ context wraps the PHP implementation. - * @var \ZMQContext $context - */ - $context = new React\ZMQ\Context($loop); - - // Reply socket for requests from CMS - $responder = $context->getSocket(ZMQ::SOCKET_REP); - $responder->bind($config->listenOn); - - // Set RESP socket options - if (isset($config->ipv6RespSupport) && $config->ipv6RespSupport === true) { - $log->debug('RESP MQ Setting socket option for IPv6 to TRUE'); - $responder->setSockOpt(\ZMQ::SOCKOPT_IPV6, true); - } +$log = new Logger('xmr'); +$log->pushHandler(new StreamHandler(STDOUT, $logLevel)); - // Pub socket for messages to Players (subs) - $publisher = $context->getSocket(ZMQ::SOCKET_PUB); +// Queue settings +$queuePoll = $config->queuePoll ?? 5; +$queueSize = $config->queueSize ?? 10; - // Set PUB socket options - if (isset($config->ipv6PubSupport) && $config->ipv6PubSupport === true) { - $log->debug('Pub MQ Setting socket option for IPv6 to TRUE'); - $publisher->setSockOpt(\ZMQ::SOCKOPT_IPV6, true); - } +// Create a client to relay messages +$relay = new Relay( + $log, + $config->relayMessages ?? '', + $config->relayOldMessages ?? '', +); - foreach ($config->pubOn as $pubOn) { - $log->info(sprintf('Bind to %s for Publish.', $pubOn)); - $publisher->bind($pubOn); - } +// Create an in memory message queue. +$messageQueue = new Queue(); - // Create an in memory message queue. - $messageStatsEmpty = [ - 'peakQueueSize' => 0, - 'messageCounters' => [ - 'total' => 0, - 'sent' => 0, - 'qos1' => 0, - 'qos2' => 0, - 'qos3' => 0, - 'qos4' => 0, - 'qos5' => 0, - 'qos6' => 0, - 'qos7' => 0, - 'qos8' => 0, - 'qos9' => 0, - 'qos10' => 0, - ] - ]; - $messageStats = $messageStatsEmpty; - $messageQueue = []; - - // REP - $responder->on('error', function ($e) use ($log) { - $log->error($e->getMessage()); - }); +try { + $loop = Loop::get(); - $responder->on('message', function ($msg) use ($log, $responder, $publisher, &$messageQueue, &$messageStats, $messageStatsEmpty) { + // Private API + // ----------- + // Create a private API to receive messages from the CMS + $api = new Api($messageQueue, $log, $relay); + // Create a HTTP server to handle requests to the API + $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) use ($log, $api) { try { - // Log incoming message - $log->info($msg); - - if ($msg === 'stats') { - // Add the current queue size - $messageStats['currentQueueSize'] = count($messageQueue); - - // Send response - $responder->send(json_encode($messageStats), \ZMQ::MODE_DONTWAIT); - - // Reset the stats - $messageStats = $messageStatsEmpty; - } else { - // Parse the message and expect a "channel" element - $msg = json_decode($msg); - - if (!isset($msg->channel)) { - throw new InvalidArgumentException('Missing Channel'); - } - - if (!isset($msg->key)) { - throw new InvalidArgumentException('Missing Key'); - } - - if (!isset($msg->message)) { - throw new InvalidArgumentException('Missing Message'); - } - - // Respond to this message - $responder->send(true, \ZMQ::MODE_DONTWAIT); - - // Make sure QOS is set - if (!isset($msg->qos)) { - // Default to the highest priority for messages missing a QOS - $msg->qos = 10; - } - - // Add to stats - $messageStats['messageCounters']['total']++; - $messageStats['messageCounters']['qos' . $msg->qos]++; - - // Decide whether we should queue the message or send it immediately. - if ($msg->qos != 10) { - // Queue for the periodic poll to send - $log->debug('Queuing'); - $messageQueue[] = $msg; + if ($request->getMethod() !== 'POST') { + throw new Exception('Method not allowed'); + } - // Record peak queue - $currentQueueSize = count($messageQueue); - if ($currentQueueSize > $messageStats['peakQueueSize']) { - $messageStats['peakQueueSize'] = $currentQueueSize; - } - } else { - // Send Immediately - $log->debug('Sending Immediately'); - $messageStats['messageCounters']['sent']++; - $publisher->sendmulti([$msg->channel, $msg->key, $msg->message], \ZMQ::MODE_DONTWAIT); - } + $json = json_decode($request->getBody()->getContents(), true); + if ($json === false || !is_array($json)) { + throw new InvalidArgumentException('Not valid JSON'); } - } catch (InvalidArgumentException $e) { - // Return false - $responder->send(false, \ZMQ::MODE_DONTWAIT); - $log->error($e->getMessage()); + return $api->handleMessage($json); + } catch (Exception $e) { + $log->error('API: e = ' . $e->getMessage()); + return new Response( + 422, + ['Content-Type' => 'plain/text'], + $e->getMessage() + ); } }); + $socket = new React\Socket\SocketServer($config->sockets->api); + $http->listen($socket); + $http->on('error', function (Exception $exception) use ($log) { + $log->error('http: ' . $exception->getMessage()); + }); + + $log->info('HTTP listening'); + + // WS + // ---- + // Web Socket server + $messagingServer = new Server($messageQueue, $log); + $wsSocket = new React\Socket\SocketServer($config->sockets->ws); + $wsServer = new WsServer($messagingServer); + $ioServer = new IoServer( + new HttpServer($wsServer), + $wsSocket, + $loop + ); + + // Enable keep alive + $wsServer->enableKeepAlive($ioServer->loop); + + $log->info('WS listening on ' . $config->sockets->ws); + + // PUB/SUB + // ------- + // LEGACY: Pub socket for messages to Players (subs) + if ($relay->isRelayOld()) { + $log->info('Legacy: relaying old messages'); + + $publisher = null; + $relay->configureZmq(); + } else { + $log->info('Legacy: handling old messages'); + + $publisher = (new React\ZMQ\Context($loop))->getSocket(ZMQ::SOCKET_PUB); + + // Set PUB socket options + if (isset($config->ipv6PubSupport) && $config->ipv6PubSupport === true) { + $log->debug('Pub MQ Setting socket option for IPv6 to TRUE'); + $publisher->setSockOpt(\ZMQ::SOCKOPT_IPV6, true); + } + + foreach ($config->sockets->zmq as $pubOn) { + $log->info(sprintf('Bind to %s for Publish.', $pubOn)); + $publisher->bind($pubOn); + } + } // Queue Processor + // --------------- $log->debug('Adding a queue processor for every ' . $queuePoll . ' seconds'); - $loop->addPeriodicTimer($queuePoll, function() use ($log, $publisher, &$messageQueue, $queueSize, &$messageStats) { + $loop->addPeriodicTimer($queuePoll, function() use ($log, $messagingServer, $relay, $publisher, $messageQueue, $queueSize) { // Is there work to be done - if (count($messageQueue) > 0) { + if ($messageQueue->hasItems()) { $log->debug('Queue Poll - work to be done.'); - // Order the message queue according to QOS - usort($messageQueue, function($a, $b) { - return ($a->qos === $b->qos) ? 0 : (($a->qos < $b->qos) ? -1 : 1); - }); + $messageQueue->sortQueue(); + + $log->debug('Queue Poll - message queue sorted'); // Send up to X messages. for ($i = 0; $i < $queueSize; $i++) { - if ($i > count($messageQueue)) { + if ($i > $messageQueue->queueSize()) { + $log->debug('Queue Poll - queue size reached'); break; } // Pop an element - $msg = array_pop($messageQueue); + $msg = $messageQueue->getItem(); // Send - $messageStats['messageCounters']['sent']++; - $publisher->sendmulti([$msg->channel, $msg->key, $msg->message], \ZMQ::MODE_DONTWAIT); + $log->debug('Sending ' . $i); + + // Where are we sending this item? + if ($msg->isWebSocket) { + $display = $messagingServer->getDisplayById($msg->channel); + if ($display === null) { + if ($relay->isRelay()) { + $relay->relay($msg); + } else { + $log->info('Display ' . $msg->channel . ' not connected'); + } + } else { + $display->connection->send($msg->message); + } + } else if ($relay->isRelayOld()) { + $relay->relay($msg); + } else if ($publisher !== null) { + $publisher->sendmulti([$msg->channel, $msg->key, $msg->message], \ZMQ::MODE_DONTWAIT); + } else { + $log->error('No route to send'); + } - $log->debug('Popped ' . $i . ' from the queue, new queue size ' . count($messageQueue)); + $log->debug('Popped ' . $i . ' from the queue, new queue size ' . $messageQueue->queueSize()); } } }); // Periodic updater - $loop->addPeriodicTimer(30, function() use ($log, $publisher) { + $loop->addPeriodicTimer(30, function() use ($log, $messagingServer, $publisher) { $log->debug('Heartbeat...'); - $publisher->sendmulti(["H", "", ""], \ZMQ::MODE_DONTWAIT); + + // Send to all connected WS clients + $messagingServer->heartbeat(); + + // Send to PUB queue + $publisher?->sendmulti(["H", "", ""], \ZMQ::MODE_DONTWAIT); + }); + + // Key management + $loop->addPeriodicTimer(3600, function() use ($log, $messageQueue) { + $log->debug('Key management...'); + $messageQueue->expireKeys(); }); - // Run the react event loop + // Run the React event loop $loop->run(); } catch (Exception $e) { $log->error($e->getMessage()); diff --git a/src/Controller/Api.php b/src/Controller/Api.php new file mode 100644 index 0000000..8aa591e --- /dev/null +++ b/src/Controller/Api.php @@ -0,0 +1,72 @@ +. + */ +namespace Xibo\Controller; + +use Psr\Log\LoggerInterface; +use React\Http\Message\Response; +use Xibo\Entity\Queue; + +class Api +{ + public function __construct( + private readonly Queue $queue, + private readonly LoggerInterface $logger, + private readonly Relay $relay, + ) { + } + + /** + * Handle messages hitting the API + * @param array $message + * @return \React\Http\Message\Response + */ + public function handleMessage(array $message): Response + { + $type = $message['type'] ?? 'empty'; + + $this->logger->debug('handleMessage: type = ' . $type); + + if ($type === 'stats') { + // Success + return Response::json($this->queue->flushStats()); + } else if ($type === 'keys') { + // Register new keys for this CMS. + $this->queue->addKey($message['id'], $message['key']); + + // Relay new keys. + if ($this->relay->isRelay()) { + $this->relay->relayArray($message); + } + } else if ($type === 'multi') { + $this->logger->debug('Queuing multiple messages'); + foreach ($message['messages'] as $message) { + $this->queue->queueItem($message); + } + } else { + $this->logger->debug('Queuing'); + $this->queue->queueItem($message); + } + + // Success + return new Response(201); + } +} diff --git a/src/Controller/Relay.php b/src/Controller/Relay.php new file mode 100644 index 0000000..9350cea --- /dev/null +++ b/src/Controller/Relay.php @@ -0,0 +1,128 @@ +. + */ + +namespace Xibo\Controller; + + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Psr\Log\LoggerInterface; +use Xibo\Entity\Message; + +class Relay +{ + private readonly ?Client $client; + private ?\ZMQSocket $socket; + + public function __construct( + private readonly LoggerInterface $logger, + private readonly string $relayMessages, + private string $relayOldMessages, + ) { + // Create a client for us to use + if (!empty($this->relayMessages) && $this->relayMessages !== 'false') { + $this->client = new Client([ + 'base_uri' => $this->relayMessages, + ]); + } else { + $this->client = null; + } + } + + public function configureZmq(): void + { + // Create a socket for us to use. + try { + $this->socket = (new \ZMQContext())->getSocket(\ZMQ::SOCKET_REQ); + $this->socket->setSockOpt(\ZMQ::SOCKOPT_LINGER, 2000); + $this->socket->connect($this->relayOldMessages); + } catch (\Exception $exception) { + $this->socket = null; + $this->relayOldMessages = null; + + $this->logger->critical('Unable to connect to old message relay: ' + . $this->relayOldMessages . ', e = ' . $exception->getMessage()); + } + } + + public function isRelay(): bool + { + return !empty($this->relayMessages) && $this->relayMessages !== 'false'; + } + + public function isRelayOld(): bool + { + return !empty($this->relayOldMessages) && $this->relayOldMessages !== 'false'; + } + + /** + * Relay a message appropriately + * @param \Xibo\Entity\Message $message + * @return void + */ + public function relay(Message $message): void + { + if ($message->isWebSocket) { + $this->relayArray($message->jsonSerialize()); + } else { + try { + $this->socket->send(json_encode($message)); + } catch (\ZMQSocketException $socketException) { + $this->logger->error('relay: [' . $socketException->getCode() . '] ' . $socketException->getMessage()); + return; + } + + $retries = 15; + + do { + try { + $reply = $this->socket->recv(\ZMQ::MODE_DONTWAIT); + + if ($reply !== false) { + break; + } + } catch (\ZMQSocketException $socketException) { + $this->logger->error('relay: [' . $socketException->getCode() . '] ' . $socketException->getMessage()); + break; + } + + usleep(100000); + } while (--$retries); + } + } + + /** + * Relay array (only ever a message over private API) + * @param array $message + * @return void + */ + public function relayArray(array $message): void + { + try { + $this->client?->post('/', [ + 'json' => $message, + ]); + } catch (GuzzleException | \Exception $e) { + $this->logger->error('relayArray: Unable to relay, e = ' . $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/Controller/Server.php b/src/Controller/Server.php new file mode 100644 index 0000000..82df95f --- /dev/null +++ b/src/Controller/Server.php @@ -0,0 +1,180 @@ +. + */ +namespace Xibo\Controller; + +use Psr\Log\LoggerInterface; +use Ratchet\ConnectionInterface; +use Ratchet\MessageComponentInterface; +use Xibo\Entity\Display; +use Xibo\Entity\Queue; +use XiboSignage\Client\Client; + +class Server implements MessageComponentInterface +{ + /** @var Display[] */ + private array $displays = []; + private array $ids = []; + + public function __construct( + private readonly Queue $queue, + private readonly LoggerInterface $logger + ) { + } + + public function onOpen(ConnectionInterface $conn): void + { + $this->logger->debug('onOpen: ' . $conn->resourceId); + + $this->addDisplay( + $conn->resourceId, + $conn + ); + } + + public function onClose(ConnectionInterface $conn): void + { + $this->removeDisplay($conn->resourceId); + $this->logger->debug('onClose: ' . $conn->resourceId); + } + + public function onError(ConnectionInterface $conn, \Exception $e): void + { + $this->logger->debug('onError: ' . $conn->resourceId . ', e: ' . $e->getMessage()); + } + + public function onMessage(ConnectionInterface $from, $msg): void + { + $display = $this->getDisplayByResourceId($from->resourceId); + + $this->logger->debug('onMessage: ' . $display->resourceId); + + // Expect a JSON string + $json = json_decode($msg, true); + if ($json === null) { + $this->logger->error('onMessage: Invalid JSON'); + return; + } + + // We are only expecting one message, which initialises the connection. + try { + if (($json['type'] ?? 'empty') === 'init') { + // The display should pass us a key + $key = $json['key'] ?? null; + if (empty($key)) { + throw new \InvalidArgumentException('Missing key'); + } + + $channel = $json['channel'] ?? null; + if (empty($channel)) { + throw new \InvalidArgumentException('Missing channel'); + } + + // Validate the key provided + if (!$this->queue->authKey($key)) { + throw new \InvalidArgumentException('Invalid key'); + } + + // Valid key for the CMS + $this->linkDisplay($display, $channel); + } else { + throw new \Exception('Invalid message type'); + } + } catch (\Exception $e) { + $this->logger->error('onMessage: ' . $e->getMessage()); + + // Close the socket with an error (onClose gets called to remove the connection) + $display->connection->close(); + } + } + + public function heartbeat(): void + { + foreach ($this->displays as $display) { + if ($display->id !== null) { + $display->connection->send('H'); + } + } + } + + /** + * Add a display to the list of connections (unauthed at this point) + * @param string $resourceId + * @param \Ratchet\ConnectionInterface $connection + * @return \Xibo\Entity\Display + */ + private function addDisplay(string $resourceId, ConnectionInterface $connection): Display + { + $this->displays[$resourceId] = new Display($resourceId, $connection); + return $this->displays[$resourceId]; + } + + /** + * Link a display to an ID (which is the channel) + * @param \Xibo\Entity\Display $display + * @param string $id + * @return void + */ + private function linkDisplay(Display $display, string $id): void + { + // Make a pointer between this resource and the ID + $this->ids[$id] = $display->resourceId; + $display->id = $id; + } + + /** + * Remove a display + * @param string $resourceId + * @return void + */ + private function removeDisplay(string $resourceId): void + { + $display = $this->getDisplayByResourceId($resourceId); + if ($display !== null && $display->id !== null) { + unset($this->ids[$display->id]); + } + unset($this->displays[$resourceId]); + } + + /** + * Get a display by its ID (channel) + * @param string $id + * @return \Xibo\Entity\Display|null + */ + public function getDisplayById(string $id): ?Display + { + if (isset($this->ids[$id])) { + return $this->displays[$this->ids[$id]] ?? null; + } else { + return null; + } + } + + /** + * Get a display by its socket resource + * @param string $resourceId + * @return \Xibo\Entity\Display|null + */ + private function getDisplayByResourceId(string $resourceId): ?Display + { + return $this->displays[$resourceId] ?? null; + } +} diff --git a/src/Entity/Display.php b/src/Entity/Display.php new file mode 100644 index 0000000..2b7fe84 --- /dev/null +++ b/src/Entity/Display.php @@ -0,0 +1,35 @@ +. + */ +namespace Xibo\Entity; + +use Ratchet\ConnectionInterface; + +class Display +{ + public ?string $id = null; + + public function __construct( + public string $resourceId, + public ConnectionInterface $connection + ) { + } +} diff --git a/src/Entity/Message.php b/src/Entity/Message.php new file mode 100644 index 0000000..8f7e26c --- /dev/null +++ b/src/Entity/Message.php @@ -0,0 +1,42 @@ +. + */ +namespace Xibo\Entity; + +class Message implements \JsonSerializable +{ + public string $channel; + public string $key; + public string $message; + public int $qos; + public bool $isWebSocket; + + public function jsonSerialize(): array + { + return [ + 'channel' => $this->channel, + 'key' => $this->key, + 'message' => $this->message, + 'qos' => $this->qos, + 'isWebSocket' => $this->isWebSocket, + ]; + } +} diff --git a/src/Entity/Queue.php b/src/Entity/Queue.php new file mode 100644 index 0000000..a9e0b55 --- /dev/null +++ b/src/Entity/Queue.php @@ -0,0 +1,224 @@ +. + */ + +namespace Xibo\Entity; + +class Queue +{ + private array $instances = []; + + /** @var \Xibo\Entity\Message[] */ + private array $queue; + + private array $stats; + + public function __construct() + { + $this->queue = []; + $this->stats = [ + 'peakQueueSize' => 0, + 'messageCounters' => [ + 'total' => 0, + 'sent' => 0, + 'qos1' => 0, + 'qos2' => 0, + 'qos3' => 0, + 'qos4' => 0, + 'qos5' => 0, + 'qos6' => 0, + 'qos7' => 0, + 'qos8' => 0, + 'qos9' => 0, + 'qos10' => 0, + ] + ]; + + } + + public function hasItems(): bool + { + return count($this->queue); + } + + public function queueSize(): int + { + return count($this->queue); + } + + public function sortQueue(): void + { + // Order the message queue according to QOS + usort($this->queue, function($a, $b) { + return ($a->qos === $b->qos) ? 0 : (($a->qos < $b->qos) ? -1 : 1); + }); + } + + public function getItem(): Message + { + $this->stats['messageCounters']['sent']++; + + return array_pop($this->queue); + } + + /** + * @param array $message + * @return void + * @throws \InvalidArgumentException + */ + public function queueItem(array $message): void + { + $msg = new Message(); + + if (!isset($message['channel'])) { + throw new \InvalidArgumentException('Missing Channel'); + } + + if (!isset($message['key'])) { + throw new \InvalidArgumentException('Missing Key'); + } + + if (!isset($message['message'])) { + throw new \InvalidArgumentException('Missing Message'); + } + + // Make sure QOS is set + if (!isset($message['qos'])) { + // Default to the highest priority for messages missing a QOS + $message['qos'] = 10; + } + + $msg->channel = $message['channel']; + $msg->key = $message['key']; + $msg->message = $message['message']; + $msg->qos = $message['qos']; + $msg->isWebSocket = $message['isWebSocket'] ?? false; + + // Queue + $this->queue[] = $msg; + + // Update stats + $this->stats['messageCounters']['total']++; + $this->stats['messageCounters']['qos' . $msg->qos]++; + + $currentQueueSize = $this->queueSize(); + if ($currentQueueSize > $this->stats['peakQueueSize']) { + $this->stats['peakQueueSize'] = $currentQueueSize; + } + } + + public function flushStats(): array + { + $stats = $this->stats; + $stats['currentQueueSize'] = $this->queueSize(); + $this->clearStats(); + return $stats; + } + + private function clearStats(): void + { + $this->stats = [ + 'peakQueueSize' => 0, + 'messageCounters' => [ + 'total' => 0, + 'sent' => 0, + 'qos1' => 0, + 'qos2' => 0, + 'qos3' => 0, + 'qos4' => 0, + 'qos5' => 0, + 'qos6' => 0, + 'qos7' => 0, + 'qos8' => 0, + 'qos9' => 0, + 'qos10' => 0, + ] + ]; + } + + /** + * Add key. + * called by a CMS to indicate that it has generated a new key, or is refreshing an old key. + * @param string $instance + * @param string $key + * @return void + */ + public function addKey(string $instance, string $key): void + { + if (!array_key_exists($instance, $this->instances)) { + $this->instances[$instance] = ['keys' => []]; + } + + // If a key already exists push the expiry time + foreach ($this->instances[$instance]['keys'] as $index => $existingKey) { + if ($existingKey['key'] === $key) { + $this->instances[$instance]['keys'][$index]['expires'] = time() + 3600; + return; + } + } + + // Not found + $this->instances[$instance]['keys'][] = [ + 'key' => $key, + 'expires' => time() + 86400, + ]; + } + + /** + * Authenticate the provided key against our list of valid keys + * @param string $providedKey + * @return bool + */ + public function authKey(string $providedKey): bool + { + foreach ($this->instances as $instance) { + foreach ($instance['keys'] as $key) { + if ($key['key'] === $providedKey && time() < $key['expires']) { + return true; + } + } + } + + return false; + } + + /** + * Key maintenance to remove keys which have expired + * @return void + */ + public function expireKeys(): void + { + // Expire keys within each instance + foreach ($this->instances as $instanceKey => $instance) { + foreach ($instance['keys'] as $key => $value) { + // Expire any keys which are no longer in date. + if (time() >= $value['expires']) { + unset($instance['keys'][$key]); + } + } + + // Remove instances with no keys + if (count($instance['keys']) <= 0) { + unset($this->instances[$instanceKey]); + } + } + } +} diff --git a/tests/Private API.http b/tests/Private API.http new file mode 100644 index 0000000..15a61ec --- /dev/null +++ b/tests/Private API.http @@ -0,0 +1,19 @@ +POST http://localhost:8081 +Content-Type: application/json + +{ + "type": "stats" +} + +### + +POST http://localhost:8081 +Content-Type: application/json + +{ + "type": "keys", + "id": "http://localhost", + "key": "123456" +} + +### diff --git a/tests/cmsGetStats.php b/tests/cmsGetStats.php deleted file mode 100644 index 2078530..0000000 --- a/tests/cmsGetStats.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * This is a CMS send MOCK - * execute with: docker-compose exec xmr sh -c "cd /opt/xmr/tests; php cmsGetStats.php" - * - */ -require '../vendor/autoload.php'; - -try { - // Create a message and send. - send('tcp://localhost:50001', 'stats'); -} catch (Exception $e) { - echo $e->getMessage() . PHP_EOL; -} - -/** - * @param $connection - * @param $message - * @return bool|string - * @throws ZMQSocketException - */ -function send($connection, $message) -{ - echo 'Sending to ' . $connection . PHP_EOL; - - // Issue a message payload to XMR. - $context = new \ZMQContext(); - - // Connect to socket - $socket = new \ZMQSocket($context, \ZMQ::SOCKET_REQ); - $socket->connect($connection); - - // Send the message to the socket - $socket->send($message); - - // Need to replace this with a non-blocking recv() with a retry loop - $retries = 15; - $reply = false; - - do { - try { - // Try and receive - // if ZMQ::MODE_NOBLOCK/MODE_DONTWAIT is used and the operation would block boolean false - // shall be returned. - $reply = $socket->recv(\ZMQ::MODE_DONTWAIT); - - echo 'Received ' . var_export($reply, true) . PHP_EOL; - - if ($reply !== false) { - break; - } - } catch (\ZMQSocketException $sockEx) { - if ($sockEx->getCode() !== \ZMQ::ERR_EAGAIN) { - throw $sockEx; - } - } - - usleep(100000); - - } while (--$retries); - - // Disconnect socket - $socket->disconnect($connection); - - return $reply; -} \ No newline at end of file diff --git a/tests/cmsSend.php b/tests/cmsSend.php index 9100858..01cdf25 100644 --- a/tests/cmsSend.php +++ b/tests/cmsSend.php @@ -1,8 +1,8 @@ . - * - * This is a CMS send MOCK - * execute with: docker-compose exec xmr sh -c "cd /opt/xmr/tests; php cmsSend.php 1234" - * */ + +// execute with: docker-compose exec xmr sh -c "cd /opt/xmr/tests; php cmsSend.php 1234" require '../vendor/autoload.php'; $_MESSAGE_COUNT = 15; -$_ENCRYPT = true; +$_ENCRYPT = false; // Track $start = microtime(true); @@ -35,6 +33,7 @@ } $identity = $argv[1]; +$isWebSocket = ($argv[2] ?? false) === 'websocket'; // Get the Public Key $fp = fopen('key.pub', 'r'); @@ -42,12 +41,16 @@ fclose($fp); try { - // Issue a message payload to XMR. - $context = new \ZMQContext(); + //open connection + $ch = curl_init(); + + //set the url, number of POST vars, POST data + curl_setopt($ch,CURLOPT_URL, 'http://localhost:8081'); + curl_setopt($ch,CURLOPT_POST, true); + curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json')); - // Connect to socket - $socket = new \ZMQSocket($context, \ZMQ::SOCKET_REQ); - $socket->connect('tcp://localhost:50001'); + // So that curl_exec returns the contents of the cURL; rather than echoing it + curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); // Queue up a bunch of messages to see what happens for ($i = 0; $i < $_MESSAGE_COUNT; $i++) { @@ -60,26 +63,32 @@ openssl_seal($i . ' - QOS1', $message, $eKeys, [$publicKey], 'RC4'); // Create a message and send. - send($socket, [ + $fields = [ 'channel' => $identity, 'key' => base64_encode($eKeys[0]), 'message' => base64_encode($message), - 'qos' => rand(1, 10) - ]); + 'qos' => rand(1, 10), + 'isWebSocket' => $isWebSocket, + ]; } else { - send($socket, [ + $fields = [ 'channel' => $identity, 'key' => 'key', 'message' => 'message ' . $i, - 'qos' => rand(1, 10) - ]); + 'qos' => rand(1, 10), + 'isWebSocket' => $isWebSocket, + ]; } + + curl_setopt($ch,CURLOPT_POSTFIELDS, json_encode($fields)); + + //execute post + $result = curl_exec($ch); + echo $result . PHP_EOL; + usleep(50); } - - // Disconnect socket - $socket->disconnect('tcp://localhost:50001'); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } diff --git a/tests/playerReq.php b/tests/playerReq.php deleted file mode 100644 index 02e2644..0000000 --- a/tests/playerReq.php +++ /dev/null @@ -1,19 +0,0 @@ - -*/ - -$context = new ZMQContext(); - -// Socket to talk to server -echo "Connecting to hello world server…\n"; -$requester = new ZMQSocket($context, ZMQ::SOCKET_REQ); -$requester->connect("tcp://192.168.86.88:58587"); -echo "connected\n"; -$requester->send("Hello"); -echo "sent\n"; -$reply = $requester->recv(); -echo "Received reply " . $reply; \ No newline at end of file diff --git a/tests/playerSub.php b/tests/playerSub.php index 6b9aed1..3481e17 100644 --- a/tests/playerSub.php +++ b/tests/playerSub.php @@ -1,8 +1,8 @@ . - * - * This is a player subscription mock file. - * docker-compose exec xmr sh -c "cd /opt/xmr/tests; php playerSub.php 1234" - * */ +// docker-compose exec xmr sh -c "cd /opt/xmr/tests; php playerSub.php 1234" +// docker-compose exec xmr sh -c "cd /opt/xmr/tests; php playerSub.php 1234 websocket" require '../vendor/autoload.php'; if (!isset($argv[1])) { @@ -35,7 +33,7 @@ $privateKey = openssl_get_privatekey(fread($fp, 8192)); fclose($fp); -echo 'Sub to: ' . $identity; +echo 'Sub to: ' . $identity . PHP_EOL; // Sub $loop = React\EventLoop\Factory::create();