├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── tests.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── reporting-api.php └── src ├── Controllers └── LogReportController.php ├── LogEndpoint.php ├── LogGroup.php ├── LogGroups.php ├── Middleware ├── AddReportToHeader.php └── ReportoEnabled.php └── ReportoServiceProvider.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: "devdavido" 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | commit-message: 8 | prefix: "build(github-actions)" 9 | schedule: 10 | interval: "monthly" 11 | labels: 12 | - "dependabot" 13 | 14 | - package-ecosystem: "composer" 15 | directory: "/" 16 | commit-message: 17 | prefix: "build(dependencies)" 18 | schedule: 19 | interval: "monthly" 20 | labels: 21 | - "dependabot" 22 | ignore: 23 | - dependency-name: "*" 24 | update-types: [ "version-update:semver-major" ] 25 | - dependency-name: "php" 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | permissions: 9 | checks: write 10 | pull-requests: write 11 | contents: read 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | php: [8.4, 8.3, 8.2, 8.1] 16 | dependency-version: [prefer-stable] 17 | os: [ubuntu-latest] 18 | 19 | name: PHP ${{ matrix.php }} – ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 2 26 | lfs: true 27 | persist-credentials: false 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | coverage: xdebug 34 | 35 | - name: Get composer cache directory 36 | id: composer-cache 37 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 38 | 39 | - name: Cache dependencies 40 | uses: actions/cache@v4 41 | with: 42 | path: ${{ steps.composer-cache.outputs.dir }} 43 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 44 | restore-keys: ${{ runner.os }}-composer-${{ matrix.dependency-version }}- 45 | 46 | - name: Install dependencies 47 | run: composer update --${{ matrix.dependency-version }} --no-interaction 48 | 49 | - name: Setup problem matches 50 | run: | 51 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 52 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 53 | 54 | - name: Execute tests 55 | run: vendor/bin/phpunit --configuration=phpunit.xml.dist --coverage-text --coverage-clover=coverage.clover 56 | 57 | - name: Upload reports for analysis 58 | run: vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-reporto` will be documented in this file. 4 | 5 | ## 1.9 - 2025-03-22 6 | - Added support for PHP 8.4 7 | - Added support for Laravel 12 8 | 9 | ## 1.8 - 2024-05-10 10 | - Added support for Laravel 11 11 | - Upgraded actions/cache to v4 12 | 13 | ## 1.7 - 2024-01-02 14 | - Added support for PHP 8.1 - 8.3 15 | - Dropped support for PHP 7.3 - 7.4 16 | - Added support for Laravel 9 - 10 17 | - Dropped support for Laravel 6 - 8 18 | - Updated dependency packages 19 | - Improved type declarations 20 | 21 | ## 1.6 - 2021-01-20 22 | - Added support for PHP 8 23 | - Updated dependency packages 24 | 25 | ## 1.5 - 2020-09-08 26 | - Added support for Laravel 8 27 | - Dropped support for PHP 7.2 28 | - Updated dependency packages 29 | 30 | ## 1.4 - 2020-03-19 31 | - Dropped support for PHP 7.0 / 7.1 32 | - Dropped support for Laravel 5.8 33 | - Added support for PHP 7.4 34 | - Added support for Laravel 7 35 | - Updated dependency packages 36 | 37 | ## 1.3 - 2019-10-02 38 | - Added support for Laravel 6 39 | 40 | ## 1.2.1 - 2019-03-09 41 | - Fix for Laravel 5.8 support: Replaced str_is with Str::is 42 | 43 | ## 1.2 - 2019-03-08 44 | - Added support for Laravel 5.8 45 | 46 | ## 1.1 - 2018-12-26 47 | - Added config option `exclude_source_files` 48 | - Dropped active support for PHP 7.1 (EOL) 49 | 50 | ## 1.0 - 2018-09-08 51 | - Initial release 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) DevDavido (https://github.com/DevDavido) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reporto: Report browser errors to the server 2 | 3 | [![Latest Version](https://img.shields.io/github/v/release/DevDavido/laravel-reporto.svg?style=flat-square)](https://github.com/DevDavido/laravel-reporto/releases) 4 | [![Build Status](https://img.shields.io/github/actions/workflow/status/DevDavido/laravel-reporto/tests.yml?style=flat-square)](https://github.com/DevDavido/laravel-reporto/actions/workflows/tests.yml) 5 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/DevDavido/laravel-reporto.svg?style=flat-square)](https://scrutinizer-ci.com/g/DevDavido/laravel-reporto/) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/DevDavido/laravel-reporto.svg?style=flat-square)](https://packagist.org/packages/DevDavido/laravel-reporto) 7 | 8 | This package makes use of the [W3C Reporting API](https://w3c.github.io/reporting/) and provides an easy plug-and-play package for your existing project. It will automatically add the necessary Report HTTP headers and log all configured browser errors to your Laravel backend. 9 | 10 | ## Documentation 11 | 12 | Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving this package? Feel free to [create an issue on GitHub](https://github.com/devdavido/laravel-reporto/issues), we'll try to address it as soon as possible. 13 | 14 | If you've found a bug regarding security please mail github{at}diskoboss{døt}de instead of using the issue tracker. 15 | 16 | ## Minimum requirements 17 | 18 | - PHP 8.0+ 19 | - Laravel 9+ 20 | 21 | For support of older PHP / Laravel versions, check out previous releases of this package. 22 | 23 | ## Installation 24 | 25 | You can install this package via composer using this command: 26 | 27 | ```bash 28 | composer require devdavido/laravel-reporto 29 | ``` 30 | 31 | The package will automatically register itself and add a `Report-To` header to your `web` routes. 32 | Each error or violation will be logged to the backend. 33 | 34 | You can publish the config-file with: 35 | 36 | ```bash 37 | php artisan vendor:publish --provider="DevDavido\Reporto\ReportoServiceProvider" --tag="config" 38 | ``` 39 | 40 | This is the contents of the published config file: 41 | 42 | ```php 43 | return [ 44 | /* 45 | * Use this setting to enable the reporting API header 46 | */ 47 | 'enabled' => env('REPORTING_API_ENABLED', true), 48 | 49 | /* 50 | * Enables the reporting API for all subdomains 51 | */ 52 | 'include_subdomains' => env('REPORTING_API_INCLUDE_SUBDOMAINS', false), 53 | 54 | /* 55 | * Exclude certain source files from being logged 56 | */ 57 | 'exclude_source_files' => env('REPORTING_API_EXCLUDE_SOURCE_FILES', ['chrome-extension://*']), 58 | 59 | /* 60 | * Defines cached lifetime of all endpoint in seconds (86400s = 1 day) 61 | */ 62 | 'endpoint_max_age' => env('REPORTING_API_MAX_AGE', 86400), 63 | 64 | /* 65 | * Which types of browser errors to report 66 | * @see https://w3c.github.io/reporting/ 67 | */ 68 | 'groups' => [ 69 | 'default', 70 | 'csp-endpoint', 71 | 'network-errors' 72 | ], 73 | 74 | /* 75 | * If you want to set the logging route prefix 76 | */ 77 | 'route_prefix' => 'log' 78 | ]; 79 | ``` 80 | 81 | ## Support me 82 | 83 | If you installed the package and it was useful for you or your business, please don't hesitate to make a donation. Thank you! 84 | 85 | Buy me a coffee 86 | 87 | ## Testing 88 | 89 | You can run the tests with: 90 | 91 | ```bash 92 | vendor/bin/phpunit 93 | ``` 94 | 95 | ## Ideas / ToDo 96 | 97 | - Daily/weekly reports via email 98 | - Multiple endpoints 99 | - More unit tests 100 | 101 | ## Changelog 102 | 103 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 104 | 105 | ## Security 106 | 107 | If you discover any security related issues, please email github{at}diskoboss{døt}de instead of using the issue tracker. 108 | 109 | ## Credits 110 | 111 | - [Freek Van der Herten](https://github.com/freekmurze) for letting me use his packages as boilerplate. 112 | 113 | ## License 114 | 115 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 116 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devdavido/laravel-reporto", 3 | "description": "Log browser errors to the server using W3C Reporting API", 4 | "keywords": [ 5 | "laravel-reporto", 6 | "laravel", 7 | "report-api", 8 | "browser", 9 | "frontend", 10 | "csp", 11 | "deprecations", 12 | "browser-interventions", 13 | "feature-policy", 14 | "network-error", 15 | "crash-report" 16 | ], 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "DevDavido", 21 | "homepage": "https://github.com/DevDavido" 22 | } 23 | ], 24 | "require": { 25 | "php": "^8.0", 26 | "ext-json": "*", 27 | "laravel/framework": "^9.0|^10.0|^11.0|^12.0" 28 | }, 29 | "require-dev": { 30 | "orchestra/testbench": "^8.0", 31 | "phpunit/phpunit" : "^10.0", 32 | "scrutinizer/ocular": "^1.9" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "DevDavido\\Reporto\\": "src" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "DevDavido\\Reporto\\Test\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "test": "vendor/bin/phpunit" 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "extra": { 51 | "laravel": { 52 | "providers": [ 53 | "DevDavido\\Reporto\\ReportoServiceProvider" 54 | ] 55 | } 56 | }, 57 | "minimum-stability": "dev", 58 | "prefer-stable": true 59 | } 60 | -------------------------------------------------------------------------------- /config/reporting-api.php: -------------------------------------------------------------------------------- 1 | env('REPORTING_API_ENABLED', true), 8 | 9 | /* 10 | * Enables the reporting API for all subdomains 11 | */ 12 | 'include_subdomains' => env('REPORTING_API_INCLUDE_SUBDOMAINS', false), 13 | 14 | /* 15 | * Exclude certain source files from being logged 16 | */ 17 | 'exclude_source_files' => env('REPORTING_API_EXCLUDE_SOURCE_FILES', ['chrome-extension://*']), 18 | 19 | /* 20 | * Defines cached lifetime of all endpoint in seconds (86400s = 1 day) 21 | */ 22 | 'endpoint_max_age' => env('REPORTING_API_MAX_AGE', 86400), 23 | 24 | /* 25 | * Which types of browser errors to report 26 | * @see https://w3c.github.io/reporting/ 27 | */ 28 | 'groups' => [ 29 | 'default', 30 | 'csp-endpoint', 31 | 'network-errors' 32 | ], 33 | 34 | /* 35 | * If you want to set the logging route prefix 36 | */ 37 | 'route_prefix' => 'log' 38 | ]; -------------------------------------------------------------------------------- /src/Controllers/LogReportController.php: -------------------------------------------------------------------------------- 1 | header('Content-Type')), $allowedContentTypes), 406); 42 | 43 | $payload = collect($request->json()); 44 | abort_if($payload->isEmpty(), 400); 45 | 46 | $report = $payload->reject(function($value) { 47 | if (!isset($value['body']['sourceFile'])) return false; 48 | return Str::is(config('reporto.exclude_source_files'), $value['body']['sourceFile']); 49 | }); 50 | 51 | if ($report->isNotEmpty()) { 52 | Log::error('Report API report:', $report->all()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/LogEndpoint.php: -------------------------------------------------------------------------------- 1 | url = route('reporto.' . Str::slug($name)); 23 | $this->priority = $priority; 24 | } 25 | 26 | /** 27 | * @return array 28 | */ 29 | public function jsonSerialize(): array 30 | { 31 | return get_object_vars($this); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LogGroup.php: -------------------------------------------------------------------------------- 1 | group = $group; 27 | $this->max_age = $max_age; 28 | $this->endpoints = [new LogEndpoint($group)]; 29 | $this->include_subdomains = $include_subdomains; 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | public function jsonSerialize(): array 36 | { 37 | return get_object_vars($this); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LogGroups.php: -------------------------------------------------------------------------------- 1 | groups = collect(); 17 | collect(config('reporto.groups'))->each(function($group) { 18 | $this->groups->push($this->createLogGroup($group)); 19 | }); 20 | } 21 | 22 | /** 23 | * @param $name string Log group name 24 | * @return LogGroup 25 | */ 26 | public function createLogGroup(string $name): LogGroup 27 | { 28 | return new LogGroup($name, config('reporto.endpoint_max_age'), config('reporto.include_subdomains')); 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function __toString() 35 | { 36 | return trim($this->groups->toJson(JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), '[]'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Middleware/AddReportToHeader.php: -------------------------------------------------------------------------------- 1 | logGroups = $groups; 27 | } 28 | 29 | /** 30 | * Adds Report-To header to response. 31 | * 32 | * @param Request $request 33 | * @param Closure $next 34 | * @return mixed 35 | */ 36 | public function handle(Request $request, Closure $next): mixed 37 | { 38 | $response = $next($request); 39 | if (!$response instanceof Response || $response->headers->has(self::REPORT_TO)) { 40 | return $response; 41 | } 42 | $response->headers->set(self::REPORT_TO, (string) $this->logGroups); 43 | 44 | return $response; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Middleware/ReportoEnabled.php: -------------------------------------------------------------------------------- 1 | publishes([ 23 | __DIR__ . '/../config/reporting-api.php' => config_path('reporting-api.php') 24 | ], 'config'); 25 | } 26 | 27 | /** 28 | * Register the application services. 29 | * 30 | * @return void 31 | */ 32 | public function register(): void 33 | { 34 | $this->mergeConfigFrom(__DIR__ . '/../config/reporting-api.php', 'reporto'); 35 | 36 | if ($this->getConfig()->get('reporto.enabled')) { 37 | $this->getRouter()->pushMiddlewareToGroup('web', AddReportToHeader::class); 38 | $this->registerRoutes(); 39 | } 40 | } 41 | 42 | /** 43 | * Register package routes to application. 44 | * 45 | * @return void 46 | */ 47 | public function registerRoutes(): void 48 | { 49 | $routeConfig = [ 50 | 'namespace' => 'DevDavido\Reporto\Controllers', 51 | 'prefix' => $this->getConfig()->get('reporto.route_prefix'), 52 | 'middleware' => [ReportoEnabled::class] 53 | ]; 54 | $this->getRouter()->group($routeConfig, function($router) { 55 | (new Collection($this->getConfig()->get('reporto.groups')))->each(function($group) use ($router) { 56 | $router->post(Str::slug($group), [ 57 | 'uses' => 'LogReportController@handle', 58 | 'as' => 'reporto.' . Str::slug($group) 59 | ]); 60 | }); 61 | }); 62 | } 63 | 64 | /** 65 | * @return Router 66 | */ 67 | public function getRouter(): Router 68 | { 69 | return $this->app['router']; 70 | } 71 | 72 | /** 73 | * @return Repository 74 | */ 75 | public function getConfig(): Repository 76 | { 77 | return $this->app['config']; 78 | } 79 | } 80 | --------------------------------------------------------------------------------