diff --git a/README.md b/README.md index 1ae286b..381690f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ As Composer dependency: ```shell # Make a copy of the dist config and edit it -$ cp mercureact.conf.dist mercurect.conf +$ cp mercureact.conf.dist mercureact.conf $ editor mercureact.conf # Use the config file when launching $ ./mercureact.phar -c mercureact.conf @@ -52,3 +52,4 @@ $ ./mercureact.phar -c mercureact.conf * [x] Distribute events over WS * [x] Break out HTTP middleware into classes * [ ] HTTP middleware unittests +* [ ] Replay missed events based on event id diff --git a/bin/mercureactd b/bin/mercureactd index e63337f..a256e02 100755 --- a/bin/mercureactd +++ b/bin/mercureactd @@ -6,9 +6,15 @@ use NoccyLabs\Mercureact\Daemon; require_once __DIR__."/../vendor/autoload.php"; -$config = Configuration::createDefault() - ->setAllowAnonymousSubscribe(true) - ->setJwtSecret("!ChangeThisMercureHubJWTSecretKey!"); +$opts = getopt("c:"); + +if (isset($opts['c'])) { + $config = Configuration::fromFile($opts['c']); +} else { + $config = Configuration::createDefault() + ->setAllowAnonymousSubscribe(true) + ->setJwtSecret("!ChangeThisMercureHubJWTSecretKey!"); +} $daemon = new Daemon($config); $daemon->start(); \ No newline at end of file diff --git a/composer.json b/composer.json index de85845..c70d012 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,9 @@ "noccylabs/simple-jwt": "^0.2.1", "noccylabs/react-websocket": "0.1.3.1", "monolog/monolog": "^3.5", - "symfony/uid": "^7.0" + "symfony/uid": "^7.0", + "rize/uri-template": "^0.3.6", + "symfony/yaml": "^7.0" }, "require-dev": { "phpunit/phpunit": "^11.0", diff --git a/composer.lock b/composer.lock index e587b72..a059db8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "794f155397a10dd2b61efedc40a661e8", + "content-hash": "4cbb3928fa6feaff2a86a1154e983a3b", "packages": [ { "name": "evenement/evenement", @@ -1284,6 +1284,147 @@ }, "time": "2018-05-29T20:21:04+00:00" }, + { + "name": "rize/uri-template", + "version": "0.3.6", + "source": { + "type": "git", + "url": "https://github.com/rize/UriTemplate.git", + "reference": "34efe65c79710eed0883884f2285ae6d4a0aad19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rize/UriTemplate/zipball/34efe65c79710eed0883884f2285ae6d4a0aad19", + "reference": "34efe65c79710eed0883884f2285ae6d4a0aad19", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Rize\\": "src/Rize" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marut K", + "homepage": "http://twitter.com/rezigned" + } + ], + "description": "PHP URI Template (RFC 6570) supports both expansion & extraction", + "keywords": [ + "RFC 6570", + "template", + "uri" + ], + "support": { + "issues": "https://github.com/rize/UriTemplate/issues", + "source": "https://github.com/rize/UriTemplate/tree/0.3.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rezigned", + "type": "custom" + }, + { + "url": "https://github.com/rezigned", + "type": "github" + }, + { + "url": "https://opencollective.com/rize-uri-template", + "type": "open_collective" + } + ], + "time": "2024-03-10T08:07:49+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.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-01-29T20:11:03+00:00" + }, { "name": "symfony/polyfill-uuid", "version": "v1.29.0", @@ -1436,6 +1577,77 @@ } ], "time": "2024-01-23T15:02:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.0.3" + }, + "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-01-23T15:02:46+00:00" } ], "packages-dev": [ diff --git a/mercureactd.conf.dist b/mercureactd.conf.dist new file mode 100644 index 0000000..4272ec9 --- /dev/null +++ b/mercureactd.conf.dist @@ -0,0 +1,34 @@ +# Mercureact default configuration file +# Please make a copy of me before editing + +listen: + - address: 0.0.0.0:9000 + + # Setup CORS headers + cors: + # Access-Control-Allow-Origin + allow_origin: '*' + # Content-Security-Policy + csp: "default-src * 'self' http: 'unsafe-eval' 'unsafe-inline'; connect-src * 'self'" + + # Setup encryption + encryption: + cert: foo.pem + key: foo.key + + # Enable websockets + websocket: + enable: true + +publish: + # Assign a UUID to published messages even if one is already set in the message + overwrite_id: false + # Reject messages with previously seen IDs + reject_duplicates: true + +subscribe: + # Allow anonymous subscription for public updates + allow_anonymous: false + +security: + jwt_secret: "!ChangeThisMercureHubJWTSecretKey!" \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..0e66c06 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Configuration.php b/src/Configuration.php index 88d3784..783fa23 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -2,6 +2,8 @@ namespace NoccyLabs\Mercureact; +use Symfony\Component\Yaml\Yaml; + class Configuration { private ?string $publicUrl = null; @@ -15,6 +17,27 @@ class Configuration return new Configuration(); } + public static function fromFile(string $file): Configuration + { + $config = new Configuration(); + + $yaml = Yaml::parseFile($file); + + if (isset($yaml['security'])) { + $security = $yaml['security']; + if (isset($security['jwt_secret'])) + $config->setJwtSecret($security['jwt_secret']); + } + + if (isset($yaml['subscribe'])) { + $subscribe = $yaml['subscribe']; + if (isset($subscribe['allow_anonymous'])) + $config->setAllowAnonymousSubscribe(boolval($subscribe['allow_anonymous'])); + } + + return $config; + } + public function setPublicUrl(string $publicUrl): self { $this->publicUrl = $publicUrl;