diff --git a/app/Http/Controllers/PluginController.php b/app/Http/Controllers/PluginController.php index 5e01a32..3a96312 100644 --- a/app/Http/Controllers/PluginController.php +++ b/app/Http/Controllers/PluginController.php @@ -19,7 +19,7 @@ public function __construct(private RuneliteApiService $runeliteApi) {} public function index(Request $request): Response { $params = $request->only(['range']); - $plugins = $this->runeliteApi->getPlugins($params); + $plugins = $this->runeliteApi->getClientPlugins($params); SEOTools::setTitle('RuneLite Plugin Stats — Browse All Plugins'); SEOTools::setDescription('Browse install counts, all-time highs for every RuneLite plugins.'); diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 0c77b22..b56808c 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -43,7 +43,7 @@ public function share(Request $request): array 'name' => config('app.name'), 'apiUrl' => rtrim(config('services.runelite_api.client_url'), '/'), 'appUrl' => config('app.url'), - 'plugins' => $this->runeliteApi->getPlugins([]), + 'plugins' => $this->runeliteApi->getClientPlugins([]), ]; } } diff --git a/app/Services/RuneliteApiService.php b/app/Services/RuneliteApiService.php index d4774d6..6c6154e 100644 --- a/app/Services/RuneliteApiService.php +++ b/app/Services/RuneliteApiService.php @@ -12,6 +12,26 @@ class RuneliteApiService /** @var array{0: array>, 1: array>}|null */ private ?array $developerLookups = null; + /** + * Fields the browser actually reads off the shared plugin list: the homepage + * table and the header search. This list is embedded in every Inertia + * response, so anything the client never touches is dead weight on the wire. + * + * @var list + */ + private const CLIENT_PLUGIN_FIELDS = [ + 'id', + 'name', + 'display', + 'author', + 'description', + 'tags', + 'warning', + 'updated_on', + 'all_time_high', + 'current_installs', + ]; + public function __construct() { $this->baseUrl = rtrim(config('services.runelite_api.url'), '/'); @@ -25,6 +45,23 @@ public function getPlugins(array $params = []): array }); } + /** + * The plugin list as it is handed to the client, stripped to the fields the + * frontend reads. `git_repo`, `support` and `created_on` are only ever read + * off the single-plugin and ranking-entry props, never off this list. + * + * @return array + */ + public function getClientPlugins(array $params = []): array + { + $keep = array_flip(self::CLIENT_PLUGIN_FIELDS); + + return array_map( + fn (array $plugin): array => array_intersect_key($plugin, $keep), + $this->getPlugins($params), + ); + } + /** @return array|null */ public function getPlugin(string $name, array $params = []): ?array { diff --git a/tests/Feature/ClientPluginPayloadTest.php b/tests/Feature/ClientPluginPayloadTest.php new file mode 100644 index 0000000..cb3d4b3 --- /dev/null +++ b/tests/Feature/ClientPluginPayloadTest.php @@ -0,0 +1,75 @@ +fakeRuneliteApi(); + + $plugins = $this->sharedPlugins(); + + $this->assertCount(1, $plugins); + + foreach (['git_repo', 'support', 'created_on'] as $dropped) { + $this->assertArrayNotHasKey($dropped, $plugins[0]); + } + } + + public function test_it_keeps_every_field_the_table_and_search_read(): void + { + $this->fakeRuneliteApi(); + + $plugins = $this->sharedPlugins(); + + foreach ([ + 'id', 'name', 'display', 'author', 'description', 'tags', + 'warning', 'updated_on', 'all_time_high', 'current_installs', + ] as $kept) { + $this->assertArrayHasKey($kept, $plugins[0]); + } + } + + /** @return array> */ + private function sharedPlugins(): array + { + $response = $this->get(route('home')); + + $response->assertOk(); + + return $response->viewData('page')['props']['plugins']; + } + + private function fakeRuneliteApi(): void + { + $plugin = [ + 'id' => 1, + 'name' => 'example-plugin', + 'git_repo' => 'https://github.com/example/example-plugin.git', + 'display' => 'Example Plugin', + 'author' => 'Example Author', + 'support' => 'https://discord.gg/example', + 'description' => 'An example plugin.', + 'tags' => 'example,test', + 'warning' => '', + 'created_on' => '2024-01-01T00:00:00.000Z', + 'updated_on' => '2024-06-01T00:00:00.000Z', + 'all_time_high' => 200, + 'current_installs' => 100, + ]; + + Http::fake([ + '*/plugins' => Http::response(['success' => true, 'data' => [$plugin]]), + '*/developers' => Http::response(['success' => true, 'data' => ['developers' => []]]), + '*' => Http::response(['success' => true, 'data' => []]), + ]); + } +}