diff --git a/README.md b/README.md index 6dba638..308fe9b 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,12 @@ The `config/plausible-proxy.php` file allows you to customize the Plausible doma ```php return [ 'domain' => env('PLAUSIBLE_DOMAIN', 'https://plausible.io'), + 'script' => env('PLAUSIBLE_SCRIPT', 'js/script.js'), ]; ``` +`PLAUSIBLE_SCRIPT` is the path of the script on your Plausible instance. Whatever script you choose, it is always served locally from `/js/script.js`. + ## Usage ### 1. Proxying the JavaScript file @@ -53,6 +56,32 @@ After installation, you can include the script like this: The route `/js/script.js` is handled automatically and caches the script for 6 hours. +#### Site-specific script (pa-XXXXX.js) + +Plausible's current installation instructions use a site-specific script such as `https://plausible.io/js/pa-12345ABCDE.js`, where optional measurements (outbound links, file downloads, etc.) are configured in the Plausible dashboard. To use it, set the script path in your `.env`: + +```env +PLAUSIBLE_SCRIPT=js/pa-12345ABCDE.js +``` + +Then load it locally and tell it to send events through the proxy with the `endpoint` option: + +```html + + +``` + +#### Legacy script extensions + +Legacy script variants work the same way, for example: + +```env +PLAUSIBLE_SCRIPT=js/script.outbound-links.js +``` + ### 2. Sending events to Plausible 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. diff --git a/config/plausible-proxy.php b/config/plausible-proxy.php index c6b5b83..11bbf28 100644 --- a/config/plausible-proxy.php +++ b/config/plausible-proxy.php @@ -3,5 +3,17 @@ // Config for Laravel Plausible Proxy return [ + /* + * The Plausible instance to proxy, e.g. https://plausible.io or your + * self-hosted instance. + */ 'domain' => env('PLAUSIBLE_DOMAIN', 'https://plausible.io'), + + /* + * Path of the script to fetch from the Plausible instance. Use this for + * site-specific scripts (e.g. js/pa-12345ABCDE.js) or legacy script + * extensions (e.g. js/script.outbound-links.js). The script is always + * served locally from /js/script.js. + */ + 'script' => env('PLAUSIBLE_SCRIPT', 'js/script.js'), ]; diff --git a/src/Http/Controllers/PlausibleScriptProxyController.php b/src/Http/Controllers/PlausibleScriptProxyController.php index b8b3105..5edd2d5 100644 --- a/src/Http/Controllers/PlausibleScriptProxyController.php +++ b/src/Http/Controllers/PlausibleScriptProxyController.php @@ -12,18 +12,14 @@ class PlausibleScriptProxyController { public function __invoke(): Response { + $url = rtrim((string) config('plausible-proxy.domain'), '/') + .'/'.ltrim((string) config('plausible-proxy.script', 'js/script.js'), '/'); + try { $script = Cache::remember( - key: 'plausible_script_js', + key: 'plausible_script_js:'.md5($url), ttl: now()->addHours(6), - callback: function () { - $plausibleDomain = config('plausible-proxy.domain'); - - return Http::timeout(5) - ->get("{$plausibleDomain}/js/script.js") - ->throw() - ->body(); - } + callback: fn () => Http::timeout(5)->get($url)->throw()->body(), ); } catch (RequestException $e) { Log::error('Failed to fetch Plausible script: '.$e->getMessage()); diff --git a/tests/PlausibleProxyTest.php b/tests/PlausibleProxyTest.php index d14a346..7aa31fd 100644 --- a/tests/PlausibleProxyTest.php +++ b/tests/PlausibleProxyTest.php @@ -1,35 +1,65 @@ addHours(6)); - - $response = $this->get('/js/script.js'); - - $response->assertStatus(200) - ->assertHeader('Content-Type', 'application/javascript') - ->assertSee('Cached script'); -}); - -it('fetches plausible script if not cached', function () { +it('fetches plausible script and caches it', function () { Http::fake([ - config('plausible-proxy.domain').'/js/script.js' => Http::response('console.log("Plausible script");', 200), + 'https://plausible.io/js/script.js' => Http::response('console.log("Plausible script");', 200), ]); - $response = $this->get('/js/script.js'); - - $response->assertStatus(200) + $this->get('/js/script.js') + ->assertStatus(200) ->assertHeader('Content-Type', 'application/javascript') ->assertSee('Plausible script'); - expect(Cache::get('plausible_script_js'))->toBe('console.log("Plausible script");'); + $this->get('/js/script.js') + ->assertStatus(200) + ->assertSee('Plausible script'); + + Http::assertSentCount(1); +}); + +it('fetches a custom script path', function () { + config()->set('plausible-proxy.script', 'js/pa-12345ABCDE.js'); + + Http::fake([ + 'https://plausible.io/js/pa-12345ABCDE.js' => Http::response('console.log("Custom script");', 200), + ]); + + $this->get('/js/script.js') + ->assertStatus(200) + ->assertSee('Custom script'); + + Http::assertSent(fn ($request) => $request->url() === 'https://plausible.io/js/pa-12345ABCDE.js'); +}); + +it('handles trailing and leading slashes in domain and script path', function () { + config()->set('plausible-proxy.domain', 'https://plausible.example.com/'); + config()->set('plausible-proxy.script', '/js/script.outbound-links.js'); + + Http::fake([ + 'https://plausible.example.com/js/script.outbound-links.js' => Http::response('console.log("Outbound");', 200), + ]); + + $this->get('/js/script.js')->assertStatus(200)->assertSee('Outbound'); +}); + +it('does not serve a cached script after the script path changes', function () { + Http::fake([ + 'https://plausible.io/js/script.js' => Http::response('console.log("Old script");', 200), + 'https://plausible.io/js/pa-12345ABCDE.js' => Http::response('console.log("New script");', 200), + ]); + + $this->get('/js/script.js')->assertSee('Old script'); + + config()->set('plausible-proxy.script', 'js/pa-12345ABCDE.js'); + + $this->get('/js/script.js')->assertSee('New script'); }); it('handles failed script fetch', function () { Http::fake([ - config('plausible-proxy.domain').'/js/script.js' => Http::response('', 500), + 'https://plausible.io/js/script.js' => Http::response('', 500), ]); $response = $this->get('/js/script.js');