diff --git a/composer.json b/composer.json index 6cf6f0c4..d26a3e36 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "symfony/console": "^6.2|^7.0" }, "require-dev": { + "guzzlehttp/guzzle": "^7.2", "roave/security-advisories": "dev-master", "orchestra/testbench": "^8.0|^9.2|^10.0", "mockery/mockery": "^1.3.3", diff --git a/config/inertia.php b/config/inertia.php index bab52f25..d79657b9 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -25,6 +25,8 @@ 'url' => env('INERTIA_SSR_URL', 'http://127.0.0.1:13714'), + 'dispatch_without_bundle' => (bool) env('INERTIA_SSR_DISPATCH_WITHOUT_BUNDLE', false), + // 'bundle' => base_path('bootstrap/ssr/ssr.mjs'), ], diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index 9f777454..9eadb09b 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -3,6 +3,7 @@ namespace Inertia\Ssr; use Exception; +use Illuminate\Http\Client\StrayRequestException; use Illuminate\Support\Facades\Http; class HttpGateway implements Gateway @@ -12,15 +13,25 @@ class HttpGateway implements Gateway */ public function dispatch(array $page): ?Response { - if (! config('inertia.ssr.enabled', true) || ! (new BundleDetector)->detect()) { + if (! config('inertia.ssr.enabled', true)) { return null; } - $url = str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; + if (! $this->shouldDispatchWithoutBundle() && ! $this->bundleExists()) { + return null; + } + + if (! $url = $this->getHttpUrl()) { + return null; + } try { $response = Http::post($url, $page)->throw()->json(); } catch (Exception $e) { + if ($e instanceof StrayRequestException) { + throw $e; + } + return null; } @@ -33,4 +44,28 @@ public function dispatch(array $page): ?Response $response['body'] ); } + + /** + * Determine if dispatch should proceed even if no bundle is detected. + */ + protected function shouldDispatchWithoutBundle(): bool + { + return config('inertia.ssr.dispatch_without_bundle', false); + } + + /** + * Check if an SSR bundle exists. + */ + protected function bundleExists(): bool + { + return (new BundleDetector)->detect() !== null; + } + + /** + * Get the SSR URL from the configuration, ensuring it ends with '/render'. + */ + public function getHttpUrl(): ?string + { + return str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; + } } diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index f1afdd07..f07dad69 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -27,11 +27,6 @@ class DirectiveTest extends TestCase */ protected $compiler; - /** - * Example Page Objects. - */ - protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; - protected function setUp(): void { parent::setUp(); diff --git a/tests/HttpGatewayTest.php b/tests/HttpGatewayTest.php new file mode 100644 index 00000000..a80af616 --- /dev/null +++ b/tests/HttpGatewayTest.php @@ -0,0 +1,113 @@ +gateway = new HttpGateway; + + Http::preventStrayRequests(); + } + + public function test_it_returns_null_when_ssr_is_disabled() + { + config([ + 'inertia.ssr.enabled' => false, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/ssr-bundle.js', + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_returns_null_when_no_bundle_file_is_detected() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => null, + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_uses_the_configured_http_url_when_the_bundle_file_is_detected() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/ssr-bundle.js', + ]); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response(json_encode([ + 'head' => ['SSR Test', ''], + 'body' => '
SSR Response
', + ])), + ]); + + $this->assertNotNull( + $response = $this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT]) + ); + + $this->assertEquals("SSR Test\n", $response->head); + $this->assertEquals('
SSR Response
', $response->body); + } + + public function test_it_uses_the_configured_http_url__when_bundle_file_detection_is_disabled() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.dispatch_without_bundle' => true, + 'inertia.ssr.bundle' => null, + ]); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response(json_encode([ + 'head' => ['SSR Test', ''], + 'body' => '
SSR Response
', + ])), + ]); + + $this->assertNotNull( + $response = $this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT]) + ); + + $this->assertEquals("SSR Test\n", $response->head); + $this->assertEquals('
SSR Response
', $response->body); + } + + public function test_it_returns_null_when_the_http_request_fails() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/ssr-bundle.js', + ]); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response(null, 500), + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } + + public function test_it_returns_null_when_invalid_json_is_returned() + { + config([ + 'inertia.ssr.enabled' => true, + 'inertia.ssr.bundle' => __DIR__.'/Stubs/ssr-bundle.js', + ]); + + Http::fake([ + $this->gateway->getHttpUrl() => Http::response('invalid json'), + ]); + + $this->assertNull($this->gateway->dispatch(['page' => self::EXAMPLE_PAGE_OBJECT])); + } +} diff --git a/tests/Stubs/ssr-bundle.js b/tests/Stubs/ssr-bundle.js new file mode 100644 index 00000000..d60d4f8c --- /dev/null +++ b/tests/Stubs/ssr-bundle.js @@ -0,0 +1 @@ +console.log("This is a stub for SSR bundle."); diff --git a/tests/TestCase.php b/tests/TestCase.php index 0586c719..a4e286a7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -10,6 +10,11 @@ abstract class TestCase extends Orchestra { + /** + * Example Page Objects. + */ + protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; + protected function getPackageProviders($app): array { return [