From a60e4cbdcf13febc15cb51d3a22e1440dc413846 Mon Sep 17 00:00:00 2001 From: Mathias Palmqvist Date: Thu, 12 Nov 2026 08:24:45 +0100 Subject: [PATCH] Rename package to mattitja/laravel-plausible-proxy - Rename Composer package to mattitja/laravel-plausible-proxy and namespace to Mattitja\LaravelPlausibleProxy - Point homepage and links to git.mattitja.se - Forward only User-Agent and visitor IP (X-Forwarded-For) to Plausible instead of all request headers - Forward all event fields and return Plausible's upstream response - Accept props as array or JSON string - Drop Laravel 10, upgrade Larastan to 3 and PHPStan plugins to 2 - Remove broken facade alias and dead autoload paths - Clean up README, issue template and remove invalid FUNDING.yml --- .github/FUNDING.yml | 1 - .github/ISSUE_TEMPLATE/config.yml | 13 ++-- .github/workflows/run-tests.yml | 4 -- README.md | 66 +++++++++-------- composer.json | 32 ++++----- phpstan.neon.dist | 6 +- phpunit.xml.dist | 2 +- routes/api.php | 2 +- routes/web.php | 2 +- .../PlausibleEventApiController.php | 21 +++--- .../PlausibleScriptProxyController.php | 2 +- src/Http/Requests/PlausibleEventRequest.php | 12 +++- src/LaravelPlausibleProxyServiceProvider.php | 2 +- tests/Pest.php | 2 +- tests/PlausibleProxyTest.php | 72 ++++++++++++++----- tests/TestCase.php | 14 +--- 16 files changed, 141 insertions(+), 112 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9e5cabb..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: Mattitja AB diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2c49613..c6beab5 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Ask a question - url: https://github.com/mattitjaab/laravel-plausible-proxy/discussions/new?category=q-a - about: Ask the community for help - - name: Request a feature - url: https://github.com/mattitjaab/laravel-plausible-proxy/discussions/new?category=ideas - about: Share ideas for new features + - name: Ask a question or request a feature + url: https://git.mattitja.se/Github/laravel-plausible-proxy/issues + about: Open an issue in the repository - name: Report a security issue - url: https://github.com/mattitjaab/laravel-plausible-proxy/security/policy - about: Learn how to notify us for sensitive bugs + url: mailto:hej@mattitja.se + about: Please report security issues privately by email diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2b393b7..56dc159 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -56,10 +56,6 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Remove Larastan if Laravel 12 - if: startsWith(matrix.laravel, '12.') - run: composer remove larastan/larastan --dev --no-update || true - - name: Install dependencies run: | composer require \ diff --git a/README.md b/README.md index de8814a..233bc26 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # laravel-plausible-proxy -[![Latest Version on Packagist](https://img.shields.io/packagist/v/mattitjaab/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitjaab/laravel-plausible-proxy) -[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/mattitjaab/laravel-plausible-proxy/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/mattitjaab/laravel-plausible-proxy/actions?query=workflow%3Arun-tests+branch%3Amain) -[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/mattitjaab/laravel-plausible-proxy/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/mattitjaab/laravel-plausible-proxy/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) -[![Total Downloads](https://img.shields.io/packagist/dt/mattitjaab/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitjaab/laravel-plausible-proxy) +[![Latest Version on Packagist](https://img.shields.io/packagist/v/mattitja/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitja/laravel-plausible-proxy) +[![Tests](https://git.mattitja.se/Github/laravel-plausible-proxy/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=run-tests.yml) +[![Code Style](https://git.mattitja.se/Github/laravel-plausible-proxy/actions/workflows/fix-php-code-style-issues.yml/badge.svg?branch=main)](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=fix-php-code-style-issues.yml) +[![Total Downloads](https://img.shields.io/packagist/dt/mattitja/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitja/laravel-plausible-proxy) -A Laravel package for proxying Plausible Analytics, improving performance. +A Laravel package for proxying Plausible Analytics, improving performance and privacy. ## Requirements -- Laravel 12 -- PHP 8.3, 8.4 -## 🚀 Installation +- Laravel 11 or 12 +- PHP 8.3 or 8.4 + +## Installation 1. Install the package via Composer: ```sh - composer require mattitjaab/laravel-plausible-proxy + composer require mattitja/laravel-plausible-proxy ``` 2. Publish the configuration file: @@ -23,14 +24,14 @@ A Laravel package for proxying Plausible Analytics, improving performance. php artisan vendor:publish --tag=plausible-proxy-config ``` -3. Add the following environment variables to your `.env` file: +3. Add the following environment variable to your `.env` file: ```env PLAUSIBLE_DOMAIN=https://plausible.io ``` -## ⚙️ Configuration +## Configuration -The `config/plausible-proxy.php` file allows you to customize the plausible domain: +The `config/plausible-proxy.php` file allows you to customize the Plausible domain: ```php return [ @@ -38,30 +39,42 @@ return [ ]; ``` -## 🔥 Usage +## Usage + +### 1. Proxying the JavaScript file -### **1. Proxying the JavaScript file** This package proxies Plausible's `script.js` while caching it to improve performance and reduce external requests. After installation, you can include the script like this: + ```html ``` -The route `/js/script.js` is automatically handled and caches the script for **6 hours**. +The route `/js/script.js` is handled automatically and caches the script for 6 hours. -### **2. Sending Events to Plausible** -Instead of sending events directly to plausible.io, the script is loaded from /js/script.js and proxies the request to Plausible via /api/event, helping to prevent blocking by ad blockers. +### 2. Sending events to Plausible -## ✅ Features +The script loaded from `/js/script.js` sends events to `/api/event` on your own domain, which forwards them to Plausible. This helps prevent blocking by ad blockers. -- 🚀 **Fast & Optimized**: Caches the Plausible script to reduce external calls. -- ✅ No issues with ad blockers: Proxies the request via a local API endpoint. -- 🛠 **Fully Configurable**: Easily set your own Plausible instance via `.env`. +Only the visitor's `User-Agent` and IP address (as `X-Forwarded-For`) are forwarded to Plausible, which is what Plausible needs to count unique visitors. Cookies and other request headers are never forwarded. -## 🛠 Testing +If your application runs behind a load balancer or reverse proxy, make sure Laravel's [trusted proxies](https://laravel.com/docs/requests#configuring-trusted-proxies) are configured so the correct visitor IP is resolved. + +## Upgrading from mattitjaab/laravel-plausible-proxy + +Version 2.0 renames the package and its namespace: + +1. Replace the package: + ```sh + composer remove mattitjaab/laravel-plausible-proxy + composer require mattitja/laravel-plausible-proxy + ``` +2. If you reference any package classes directly, change the namespace from `MattitjaAB\LaravelPlausibleProxy` to `Mattitja\LaravelPlausibleProxy`. +3. Laravel 10 is no longer supported. + +## Testing -You can run tests with: ```sh composer test ``` @@ -70,18 +83,13 @@ composer test Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. -## Contributing - -Please see [CONTRIBUTING](CONTRIBUTING.md) for details. - ## Security Vulnerabilities -Please review [our security policy](../../security/policy) on how to report security vulnerabilities. +Please report security vulnerabilities by email to hej@mattitja.se. ## Credits - [Mathias Palmqvist](https://github.com/mathiaspalmqvist) -- [All Contributors](../../contributors) ## License diff --git a/composer.json b/composer.json index 7512712..46f29e7 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { - "name": "mattitjaab/laravel-plausible-proxy", + "name": "mattitja/laravel-plausible-proxy", "description": "A Laravel package for proxying Plausible Analytics, improving performance and privacy.", "keywords": [ - "Mattitja AB", - "MattitjaAB", + "Mattitja", "laravel", "laravel-plausible-proxy", "plausible" ], - "homepage": "https://github.com/mattitjaab/laravel-plausible-proxy", + "homepage": "https://git.mattitja.se/Github/laravel-plausible-proxy", "license": "MIT", "authors": [ { @@ -20,31 +19,29 @@ "require": { "php": "^8.3", "spatie/laravel-package-tools": "^1.16", - "illuminate/contracts": "^10.0||^11.0||^12.0" + "illuminate/contracts": "^11.0||^12.0" }, "require-dev": { "laravel/pint": "^1.14", - "nunomaduro/collision": "^8.1.1||^7.10.0", - "larastan/larastan": "^2.9", - "orchestra/testbench": "^9.0.0||^8.22.0", + "nunomaduro/collision": "^8.1", + "larastan/larastan": "^3.0", + "orchestra/testbench": "^9.0||^10.0", "pestphp/pest": "^3.0", "pestphp/pest-plugin-arch": "^3.0", "pestphp/pest-plugin-laravel": "^3.0", "phpstan/extension-installer": "^1.3", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "spatie/laravel-ray": "^1.35" }, "autoload": { "psr-4": { - "MattitjaAB\\LaravelPlausibleProxy\\": "src/", - "MattitjaAB\\LaravelPlausibleProxy\\Database\\Factories\\": "database/factories/" + "Mattitja\\LaravelPlausibleProxy\\": "src/" } }, "autoload-dev": { "psr-4": { - "MattitjaAB\\LaravelPlausibleProxy\\Tests\\": "tests/", - "Workbench\\App\\": "workbench/app/" + "Mattitja\\LaravelPlausibleProxy\\Tests\\": "tests/" } }, "scripts": { @@ -65,11 +62,8 @@ "extra": { "laravel": { "providers": [ - "MattitjaAB\\LaravelPlausibleProxy\\LaravelPlausibleProxyServiceProvider" - ], - "aliases": { - "LaravelPlausibleProxy": "MattitjaAB\\LaravelPlausibleProxy\\Facades\\LaravelPlausibleProxy" - } + "Mattitja\\LaravelPlausibleProxy\\LaravelPlausibleProxyServiceProvider" + ] } }, "minimum-stability": "dev", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b678f5e..9f4d90f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,4 +8,8 @@ parameters: - config tmpDir: build/phpstan checkOctaneCompatibility: true - checkModelProperties: true \ No newline at end of file + checkModelProperties: true + ignoreErrors: + - + identifier: larastan.noEnvCallsOutsideOfConfig + path: config/plausible-proxy.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e8ca933..b058e3a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,7 +16,7 @@ backupStaticProperties="false" > - + tests diff --git a/routes/api.php b/routes/api.php index 399887d..bad9c8d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,6 @@ headers->all()) - ->mapWithKeys(fn ($value, $key) => [$key => implode(', ', $value)]) - ->all(); + $upstream = Http::withHeaders([ + 'User-Agent' => (string) $request->userAgent(), + 'X-Forwarded-For' => (string) $request->ip(), + ])->post(config('plausible-proxy.domain').'/api/event', $request->all()); - Http::withHeaders($headers) - ->post(config('plausible-proxy.domain').'/api/event', $request->validated()); - - return response()->json(['status' => 'queued']); + return response($upstream->body(), $upstream->status()) + ->header('Content-Type', $upstream->header('Content-Type') ?: 'text/plain'); } } diff --git a/src/Http/Controllers/PlausibleScriptProxyController.php b/src/Http/Controllers/PlausibleScriptProxyController.php index 580b3f5..b8b3105 100644 --- a/src/Http/Controllers/PlausibleScriptProxyController.php +++ b/src/Http/Controllers/PlausibleScriptProxyController.php @@ -1,6 +1,6 @@ ['required', 'string'], 'r' => ['nullable', 'url'], 'm' => ['nullable', 'string'], - 'p' => ['nullable', 'array'], + 'p' => ['nullable', function (string $attribute, mixed $value, \Closure $fail) { + if (is_array($value)) { + return; + } + + if (! is_string($value) || ! is_array(json_decode($value, true))) { + $fail("The {$attribute} field must be an object or a JSON-encoded object."); + } + }], ]; } } diff --git a/src/LaravelPlausibleProxyServiceProvider.php b/src/LaravelPlausibleProxyServiceProvider.php index a1c39cd..4232673 100644 --- a/src/LaravelPlausibleProxyServiceProvider.php +++ b/src/LaravelPlausibleProxyServiceProvider.php @@ -1,6 +1,6 @@ in(__DIR__); diff --git a/tests/PlausibleProxyTest.php b/tests/PlausibleProxyTest.php index a28f9ec..d14a346 100644 --- a/tests/PlausibleProxyTest.php +++ b/tests/PlausibleProxyTest.php @@ -49,35 +49,69 @@ it('rejects invalid event data', function () { ->assertJsonValidationErrors(['u' => 'The u field must be a valid URL.']); }); -it('accepts valid event data and forwards headers', function () { +it('forwards only user agent and client ip to plausible', function () { config()->set('plausible-proxy.domain', 'https://plausible.io'); Http::fake([ - 'https://plausible.io/api/event' => function ($request) { - return Http::response(['status' => 'queued'], 200); - }, + 'https://plausible.io/api/event' => Http::response('ok', 202), ]); - $headers = [ - 'User-Agent' => 'TestAgent', - 'Accept' => 'application/json', - ]; + $response = $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.7']) + ->postJson('/api/event', [ + 'n' => 'pageview', + 'u' => 'https://example.com/page', + 'd' => 'example.com', + ], [ + 'User-Agent' => 'TestAgent', + 'Cookie' => 'laravel_session=secret', + 'Authorization' => 'Bearer secret', + ]); - $response = $this->postJson('/api/event', [ - 'n' => 'pageview', - 'u' => 'https://example.com/page', - 'd' => 'example.com', - ], $headers); - - $response->assertStatus(200) - ->assertJson(['status' => 'queued']); + $response->assertStatus(202); + expect($response->getContent())->toBe('ok'); Http::assertSentCount(1); Http::assertSent(function ($request) { return $request->url() === 'https://plausible.io/api/event' - && isset($request['n']) && $request['n'] === 'pageview' - && isset($request['u']) && $request['u'] === 'https://example.com/page' - && isset($request['d']) && $request['d'] === 'example.com'; + && $request->header('User-Agent') === ['TestAgent'] + && $request->header('X-Forwarded-For') === ['203.0.113.7'] + && ! $request->hasHeader('Cookie') + && ! $request->hasHeader('Authorization') + && $request['n'] === 'pageview' + && $request['u'] === 'https://example.com/page' + && $request['d'] === 'example.com'; }); }); + +it('forwards additional plausible fields', function () { + config()->set('plausible-proxy.domain', 'https://plausible.io'); + + Http::fake([ + 'https://plausible.io/api/event' => Http::response('ok', 202), + ]); + + $this->postJson('/api/event', [ + 'n' => 'Purchase', + 'u' => 'https://example.com/checkout', + 'd' => 'example.com', + 'i' => false, + 'p' => '{"plan":"pro"}', + '$' => ['amount' => '9.99', 'currency' => 'EUR'], + ])->assertStatus(202); + + Http::assertSent(function ($request) { + return $request['i'] === false + && $request['p'] === '{"plan":"pro"}' + && $request['$'] === ['amount' => '9.99', 'currency' => 'EUR']; + }); +}); + +it('rejects invalid props', function () { + $this->postJson('/api/event', [ + 'n' => 'pageview', + 'u' => 'https://example.com/page', + 'd' => 'example.com', + 'p' => 'not-json', + ])->assertStatus(422)->assertJsonValidationErrors(['p']); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index c2dcc4d..e77b558 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,22 +1,12 @@ 'MattitjaAB\\LaravelPlausibleProxy\\Database\\Factories\\'.class_basename($modelName).'Factory' - ); - } - protected function getPackageProviders($app) { return [