├── .gitattributes ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config └── pwa.php ├── database └── migrations │ └── 2020_07_09_070051_create_pwa_settings_table.php ├── phpunit.xml ├── resources ├── icons │ ├── icon-128x128.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ ├── icon-192x192.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── splash-1125x2436.png │ ├── splash-1242x2208.png │ ├── splash-1242x2688.png │ ├── splash-1536x2048.png │ ├── splash-1668x2224.png │ ├── splash-1668x2388.png │ ├── splash-2048x2732.png │ ├── splash-640x1136.png │ ├── splash-750x1334.png │ └── splash-828x1792.png ├── lang │ ├── ar │ │ └── pwa.php │ ├── bn │ │ └── pwa.php │ ├── cn │ │ └── pwa.php │ ├── en │ │ └── pwa.php │ └── jp │ │ └── pwa.php └── views │ ├── meta.blade.php │ ├── offline.blade.php │ └── settings.blade.php ├── routes └── pwa.php ├── src ├── Commands │ └── InstallPwa.php ├── Facades │ └── PWA.php ├── Helpers │ └── helpers.php ├── Http │ └── Controllers │ │ ├── Controller.php │ │ └── PwaController.php ├── Model │ └── Setting.php ├── PWA.php └── PwaServiceProvider.php └── tests └── PWA.php /.gitattributes: -------------------------------------------------------------------------------- 1 | *.php linguist-language=PHP 2 | *.js linguist-language=PHP -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | node_modules/ 3 | npm-debug.log 4 | 5 | # Laravel 4 specific 6 | bootstrap/compiled.php 7 | app/storage/ 8 | 9 | # Laravel 5 & Lumen specific 10 | public/storage 11 | public/hot 12 | storage/*.key 13 | .env.*.php 14 | .env.php 15 | .env 16 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | checks: 4 | php: 5 | code_rating: true 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | tools: 20 | external_code_coverage: 21 | timeout: 600 22 | runs: 2 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.2' 5 | - '7.3' 6 | - '7.4' 7 | 8 | before_script: 9 | - travis_retry composer self-update 10 | - travis_retry composer update --prefer-lowest --prefer-source --no-interaction 11 | 12 | script: 13 | - phpunit --coverage-text --coverage-clover=coverage.clover 14 | 15 | after_script: 16 | - wget https://scrutinizer-ci.com/ocular.phar 17 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 18 | 19 | notifications: 20 | on_success: never 21 | on_failure: always -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Md Abu Ahsan Basir 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](http://badges.mit-license.org) 2 | [![Build Status](https://travis-ci.org/Codexshaper/laravel-pwa.svg?branch=master)](https://travis-ci.org/Codexshaper/laravel-pwa) 3 | [![StyleCI](https://github.styleci.io/repos/279073965/shield?branch=master)](https://github.styleci.io/repos/279073965?branch=master) 4 | [![Quality Score](https://img.shields.io/scrutinizer/g/Codexshaper/laravel-pwa.svg?style=flat-square)](https://scrutinizer-ci.com/g/Codexshaper/laravel-pwa) 5 | [![Downloads](https://poser.pugx.org/Codexshaper/laravel-pwa/d/total.svg)](https://packagist.org/packages/Codexshaper/laravel-pwa) 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/Codexshaper/laravel-pwa.svg?style=flat-square)](https://packagist.org/packages/Codexshaper/laravel-pwa) 7 | 8 | # Description 9 | Installable PWA for laravel. Implement PWA in your laravel website within 5 mins. 10 | 11 | | Lravel PWA version | Laravel version | 12 | | --- | --- | 13 | | 1.0 | ^5.6, ^6.0, ^7.0 | 14 | | 1.1 | ^8.0 | 15 | | 1.2 | ^8.0 | 16 | 17 | ## Requirements 18 | It only suppoorts HTTPS and localhost (both HTTP and HTTPS) 19 | 20 | #### Click here to see video instruction. 21 | 22 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/kDcy5cFH670/0.jpg)](https://www.youtube.com/watch?v=kDcy5cFH670) 23 | 24 | ## Download 25 | ``` 26 | composer require codexshaper/laravel-pwa 27 | ``` 28 | 29 | ## Install 30 | 31 | ``` 32 | php artisan pwa:install 33 | ``` 34 | 35 | ## Use: Add below code before closing head tag 36 | 37 | ``` 38 | {{ pwa_meta() }} 39 | ``` 40 | 41 | OR 42 | 43 | ``` 44 | @PWA 45 | ``` 46 | 47 | ## Finaly configure your own information. Go to {{url}}/pwa 48 | 49 | #### Additionaly you may add below script after all js loaded to work perfectly bootstrap 4 custom file input 50 | 51 | ``` 52 | $(".custom-file-input").on("change", function() { 53 | var fileName = $(this).val().split("\\").pop(); 54 | $(this).siblings(".custom-file-label").addClass("selected").html(fileName); 55 | }); 56 | ``` 57 | 58 | ## Contributors 59 | 60 | * **Md Abu Ahsan Basir** - *Creator and Maintainer* - [github](https://github.com/maab16) 61 | 62 | ## Concept from [silviolleite](https://github.com/silviolleite/laravel-pwa) 63 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codexshaper/laravel-pwa", 3 | "description": "Laravel Progressive Web App", 4 | "keywords": [ 5 | "laravel", "pwa", "progressive", "web", "app", 6 | "multi-tenancy", "pwa", "laravel", "multitenancy", "multitenant", "progressive-web-app", 7 | "laravel-pwa", "pwa-laravel", "google-pwa", "ios-pwa", "firefox-pwa", "safari-pwa", 8 | "ios", "android", "chrome", "safari", "firefox" 9 | ], 10 | "license": "MIT", 11 | "homepage": "https://github.com/Codexshaper/laravel-pwa", 12 | "support": { 13 | "issues": "https://github.com/Codexshaper/laravel-pwa/issues", 14 | "source": "https://github.com/Codexshaper/laravel-pwa" 15 | }, 16 | "authors": [ 17 | { 18 | "name": "Md Abu Ahsan Basir", 19 | "email": "maab.career@gmail.com" 20 | } 21 | ], 22 | "type": "library", 23 | "minimum-stability": "dev", 24 | "require": { 25 | "php": "^7.0|^7.1|^7.2|^7.3|^7.4|^8.0", 26 | "illuminate/support": "^5.6|^6.0|^7.0|^8.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^7.0|^8.0", 30 | "orchestra/testbench": "^4.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "CodexShaper\\PWA\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "CodexShaper\\PWA\\": "src/", 40 | "CodexShaper\\PWA\\Test\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "CodexShaper\\PWA\\PwaServiceProvider" 47 | ], 48 | "aliases": { 49 | "PWA": "CodexShaper\\PWA\\Facades\\PWA" 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/pwa.php: -------------------------------------------------------------------------------- 1 | 'localhost', 5 | 'prefix' => '/pwa', 6 | 'icons_path' => storage_path('/../vendor/codexshaper/laravel-pwa/resources/icons'), 7 | 'scope' => '.', 8 | ]; 9 | -------------------------------------------------------------------------------- /database/migrations/2020_07_09_070051_create_pwa_settings_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('domain', 100)->nullable(); 19 | $table->string('tenant_id', 36)->nullable(); 20 | $table->json('data')->nullable(); 21 | $table->boolean('status')->default(0); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('pwa_settings'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-128x128.png -------------------------------------------------------------------------------- /resources/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-144x144.png -------------------------------------------------------------------------------- /resources/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-152x152.png -------------------------------------------------------------------------------- /resources/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-192x192.png -------------------------------------------------------------------------------- /resources/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-384x384.png -------------------------------------------------------------------------------- /resources/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-512x512.png -------------------------------------------------------------------------------- /resources/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-72x72.png -------------------------------------------------------------------------------- /resources/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/icon-96x96.png -------------------------------------------------------------------------------- /resources/icons/splash-1125x2436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1125x2436.png -------------------------------------------------------------------------------- /resources/icons/splash-1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1242x2208.png -------------------------------------------------------------------------------- /resources/icons/splash-1242x2688.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1242x2688.png -------------------------------------------------------------------------------- /resources/icons/splash-1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1536x2048.png -------------------------------------------------------------------------------- /resources/icons/splash-1668x2224.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1668x2224.png -------------------------------------------------------------------------------- /resources/icons/splash-1668x2388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-1668x2388.png -------------------------------------------------------------------------------- /resources/icons/splash-2048x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-2048x2732.png -------------------------------------------------------------------------------- /resources/icons/splash-640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-640x1136.png -------------------------------------------------------------------------------- /resources/icons/splash-750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-750x1334.png -------------------------------------------------------------------------------- /resources/icons/splash-828x1792.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codexshaper/laravel-pwa/173a25175d940f1d1f144b6d44bae216c2eff477/resources/icons/splash-828x1792.png -------------------------------------------------------------------------------- /resources/lang/ar/pwa.php: -------------------------------------------------------------------------------- 1 | 'وزن', 7 | 'update' => 'تحديث', 8 | 'choose-image' => 'اختر صورة', 9 | 'change-icon' => 'تغيير الايقونة', 10 | 'change-splash' => 'تغيير البداية', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/bn/pwa.php: -------------------------------------------------------------------------------- 1 | 'PWA', 7 | 'update' => 'হালনাগাদ', 8 | 'choose-image' => 'চিত্র চয়ন করুন', 9 | 'change-icon' => 'প্রতীক পাল্টান', 10 | 'change-splash' => 'স্প্ল্যাশ পরিবর্তন করুন', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/cn/pwa.php: -------------------------------------------------------------------------------- 1 | '改變飛濺', 7 | 'update' => '更新資料', 8 | 'choose-image' => '選擇圖片', 9 | 'change-icon' => '變更圖示', 10 | 'change-splash' => '改變飛濺', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/en/pwa.php: -------------------------------------------------------------------------------- 1 | 'PWA', 7 | 'update' => 'Update', 8 | 'choose-image' => 'Choose Image', 9 | 'change-icon' => 'Change Icon', 10 | 'change-splash' => 'Change Splash', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/jp/pwa.php: -------------------------------------------------------------------------------- 1 | 'pwa', 7 | 'update' => '更新', 8 | 'choose-image' => '画像を選択', 9 | 'change-icon' => 'アイコンを変更', 10 | 'change-splash' => 'スプラッシュを変更する', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/views/meta.blade.php: -------------------------------------------------------------------------------- 1 | @if(isset(pwa_settings()->status) && pwa_settings()->status == 1) 2 | @php $manifest = pwa_settings()->data['manifest']; @endphp 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | @endif -------------------------------------------------------------------------------- /resources/views/offline.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | 5 |

You are currently not connected to any networks. Maybe you are offline. Please check your internet connection.

6 | @endsection -------------------------------------------------------------------------------- /resources/views/settings.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | @section('content') 3 | 44 |
45 |
46 |
47 | @if(session()->get('success')) 48 |
49 | {{ session()->get('success') }} 50 |

51 | @endif 52 | @if(!empty($errors->all())) 53 | @foreach($errors->all() as $error) 54 |
55 | {{$error }} 56 |
57 | @endforeach 58 | @endif 59 | {{-- PWA actions --}} 60 | @if(!isset($pwa->data)) 61 |
62 | @csrf 63 | 64 |
65 | @elseif(isset($pwa->data) && $pwa->status == 0) 66 |
67 | @csrf 68 | 69 |
70 |
71 | @csrf 72 | @method('delete') 73 | 74 |
75 | @elseif(isset($pwa->data) && $pwa->status == 1) 76 |
77 | @csrf 78 | 79 |
80 |
81 | @csrf 82 | @method('delete') 83 | 84 |
85 | {{-- PWA app information --}} 86 |
87 | @csrf 88 | @method('put') 89 |
90 | 91 |
92 | 93 | @if ($errors->has('name')) 94 | 95 | {{ $errors->first('name') }} 96 | 97 | @endif 98 |
99 |
100 | 101 |
102 | 103 |
104 | 105 | @if ($errors->has('short_name')) 106 | 107 | {{ $errors->first('short_name') }} 108 | 109 | @endif 110 |
111 |
112 | 113 |
114 | 115 |
116 | 117 | @if ($errors->has('start_url')) 118 | 119 | {{ $errors->first('start_url') }} 120 | 121 | @endif 122 |
123 |
124 | 125 |
126 | 127 |
128 | 129 |
130 |
131 | 132 |
133 | 134 |
135 | 136 |
137 |
138 | 139 |
140 | 141 |
142 | 148 |

For more details click here

149 |
150 |
151 | 152 | {{-- PWA icons --}} 153 | 179 | {{-- PWA splashes --}} 180 | 204 | 205 |
206 | 207 |
208 |
209 | @endif 210 |
211 |
212 |
213 | @endsection -------------------------------------------------------------------------------- /routes/pwa.php: -------------------------------------------------------------------------------- 1 | 'web'], function () { 4 | Route::get('/pwa/assets/{path?}', '\CodexShaper\PWA\Http\Controllers\PwaController@asset') 5 | ->where('path', '(.*)') 6 | ->name('pwa.asset'); 7 | Route::get( 8 | 'serviceworker', 9 | '\CodexShaper\PWA\Http\Controllers\PwaController@serviceWorker' 10 | )->name('pwa.serviceworker'); 11 | Route::get( 12 | 'register-serviceworker', 13 | '\CodexShaper\PWA\Http\Controllers\PwaController@serviceWorkerRegisterContent' 14 | )->name('pwa.serviceworker.register'); 15 | Route::get( 16 | 'offline', 17 | '\CodexShaper\PWA\Http\Controllers\PwaController@offline' 18 | )->name('pwa.offline'); 19 | Route::group(['prefix' => 'pwa'], function () { 20 | Route::get( 21 | 'manifest', 22 | '\CodexShaper\PWA\Http\Controllers\PwaController@manifest' 23 | )->name('pwa.manifest'); 24 | }); 25 | 26 | // PWA authorisez routes. 27 | Route::group(['prefix' => 'pwa', 'middleware' => 'auth'], function () { 28 | Route::get('', '\CodexShaper\PWA\Http\Controllers\PwaController@index')->name('pwa'); 29 | Route::post('store', '\CodexShaper\PWA\Http\Controllers\PwaController@store')->name('pwa.store'); 30 | Route::put('store', '\CodexShaper\PWA\Http\Controllers\PwaController@update')->name('pwa.update'); 31 | Route::delete('store', '\CodexShaper\PWA\Http\Controllers\PwaController@destroy')->name('pwa.delete'); 32 | Route::post('activate', '\CodexShaper\PWA\Http\Controllers\PwaController@activate')->name('pwa.activate'); 33 | Route::post('deactivate', '\CodexShaper\PWA\Http\Controllers\PwaController@deactivate')->name('pwa.deactivate'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/Commands/InstallPwa.php: -------------------------------------------------------------------------------- 1 | info('Publishing the PWA assets, database, and config files'); 68 | // Publish only relevant resources on install 69 | $tags = ['pwa.migrations', 'pwa.tenant.migrations', 'pwa.config', 'pwa.views', 'pwa.lang']; 70 | $this->call('vendor:publish', ['--provider' => PwaServiceProvider::class, '--tag' => $tags]); 71 | 72 | $this->info('Migrating the database tables into your application'); 73 | $this->call('migrate', ['--force' => $this->option('force')]); 74 | 75 | $this->info('Dumping the autoloaded files and reloading all new files'); 76 | $composer = $this->findComposer(); 77 | $process = Process::fromShellCommandline($composer.' dump-autoload'); 78 | $process->setTimeout(null); // Setting timeout to null to prevent installation from stopping at a certain point in time 79 | $process->setWorkingDirectory(base_path())->run(); 80 | 81 | // Load Permission routes into application's 'routes/web.php' 82 | $this->info('Adding Permission routes to routes/web.php'); 83 | $routes_contents = $filesystem->get(base_path('routes/web.php')); 84 | if (false === strpos($routes_contents, 'PWA::routes();')) { 85 | $filesystem->append( 86 | base_path('routes/web.php'), 87 | "\n\PWA::routes();\n" 88 | ); 89 | } 90 | if ($filesystem->exists(base_path('routes/tenant.php'))) { 91 | $this->info('Adding Permission routes to routes/tenant.php'); 92 | $routes_contents = $filesystem->get(base_path('routes/tenant.php')); 93 | if (false === strpos($routes_contents, 'PWA::routes();')) { 94 | $filesystem->append( 95 | base_path('routes/tenant.php'), 96 | "\n\PWA::routes();\n" 97 | ); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Facades/PWA.php: -------------------------------------------------------------------------------- 1 | $path]); 16 | } 17 | } 18 | 19 | if (!function_exists('pwa_settings')) { 20 | /** 21 | * Get current domain's PWA settings. 22 | * 23 | * @return string 24 | */ 25 | function pwa_settings() 26 | { 27 | return Setting::where('domain', '=', request()->getHttpHost())->first(); 28 | } 29 | } 30 | 31 | if (!function_exists('pwa_meta')) { 32 | /** 33 | * Generate PWA meta, link and script tags. 34 | * 35 | * @return string 36 | */ 37 | function pwa_meta() 38 | { 39 | echo view('pwa::meta')->render(); 40 | } 41 | } 42 | 43 | if (!function_exists('last_icon_src')) { 44 | /** 45 | * Get last icon from manifest set. 46 | * 47 | * @return string 48 | */ 49 | function last_icon_src() 50 | { 51 | $icons = pwa_settings()->data['manifest']['icons']; 52 | $lastIcon = end($icons); 53 | 54 | return $lastIcon['path']; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | getPwaInstance(); 22 | 23 | return view('pwa::settings', compact('pwa')); 24 | } 25 | 26 | /** 27 | * Store a newly created resource in storage. 28 | * 29 | * @param \Illuminate\Http\Request $request 30 | * 31 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse 32 | */ 33 | public function store(Request $request) 34 | { 35 | if (!File::isDirectory(storage_path('app/public/pwa/images/icons'))) { 36 | Storage::makeDirectory('public/pwa/images/icons', 0777, true); 37 | } 38 | 39 | File::copyDirectory( 40 | config('pwa.icons_path', __DIR__.'/../../../resources/icons'), 41 | storage_path('app/public/pwa/images/icons') 42 | ); 43 | 44 | $pwa = $this->getPwaInstance(); 45 | 46 | if (!$pwa) { 47 | $pwa = new Setting(); 48 | } 49 | 50 | $domain = request()->getHttpHost(); 51 | $tenant_id = null; 52 | if (function_exists('tenant') && isset(tenant()->id)) { 53 | $tenant_id = tenant()->id; 54 | } 55 | 56 | $data = $this->getManifestData([ 57 | 'name' => 'Demo App', 58 | 'short_name' => 'DA', 59 | 'start_url' => 'https://'.$domain.'/', 60 | 'background_color' => '#ffffff', 61 | 'theme_color' => '#000000', 62 | 'display' => 'standalone', 63 | ]); 64 | 65 | $data['serviceworker'] = $this->generateServiceWorker(); 66 | $data['register_serviceworker'] = $this->generateServiceWorkerRegister(); 67 | 68 | $pwa->tenant_id = $tenant_id; 69 | $pwa->domain = $domain; 70 | $pwa->data = $data; 71 | $pwa->status = 1; 72 | $pwa->save(); 73 | 74 | return redirect(route('pwa'))->with('success', 'Pwa created successfully.'); 75 | } 76 | 77 | /** 78 | * Activate PWA for the current domain. 79 | * 80 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse 81 | */ 82 | public function activate() 83 | { 84 | $pwa = $this->getPwaInstance(); 85 | $pwa->status = 1; 86 | $pwa->save(); 87 | 88 | return redirect(route('pwa'))->with('success', 'Pwa activated successfully.'); 89 | } 90 | 91 | /** 92 | * Deactivate PWA for the current domain. 93 | * 94 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse 95 | */ 96 | public function deactivate() 97 | { 98 | $pwa = $this->getPwaInstance(); 99 | $pwa->status = 0; 100 | $pwa->save(); 101 | 102 | return redirect(route('pwa'))->with('success', 'Pwa deactivated successfully.'); 103 | } 104 | 105 | /** 106 | * Update the specified resource in storage. 107 | * 108 | * @param \Illuminate\Http\Request $request 109 | * @param \CodexShaper\PWA\Model\Setting $Setting 110 | * 111 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse 112 | */ 113 | public function update(Request $request, Setting $Setting) 114 | { 115 | $request->validate([ 116 | 'name' => 'required', 117 | 'short_name' => 'required', 118 | 'start_url' => 'required', 119 | 'background_color' => 'required', 120 | 'theme_color' => 'required', 121 | 'display' => 'required', 122 | ]); 123 | if (isset($request->icons) && count($request->icons) > 0) { 124 | foreach ($request->icons as $key => $icon) { 125 | $destination_path = 'pwa/images/icons/icon-'.$key.'.png'; 126 | Storage::disk('public')->putFileAs('', $icon, $destination_path); 127 | } 128 | } 129 | 130 | if (isset($request->splashes) && count($request->splashes)) { 131 | foreach ($request->splashes as $key => $splash) { 132 | $destination_path = 'pwa/images/icons/splash-'.$key.'.png'; 133 | Storage::disk('public')->putFileAs('', $splash, $destination_path); 134 | } 135 | } 136 | 137 | $pwa = $this->getPwaInstance(); 138 | 139 | if (!$pwa) { 140 | $pwa = new Setting(); 141 | } 142 | 143 | $domain = request()->getHttpHost(); 144 | $tenant_id = null; 145 | if (function_exists('tenant') && isset(tenant()->id)) { 146 | $tenant_id = tenant()->id; 147 | } 148 | 149 | $data = $this->getManifestData([ 150 | 'name' => $request->name, 151 | 'short_name' => $request->short_name, 152 | 'start_url' => $request->start_url, 153 | 'background_color' => $request->background_color, 154 | 'theme_color' => $request->theme_color, 155 | 'display' => $request->display, 156 | ]); 157 | 158 | $data['serviceworker'] = $this->generateServiceWorker(); 159 | $data['register_serviceworker'] = $this->generateServiceWorkerRegister(); 160 | 161 | $pwa->tenant_id = $tenant_id; 162 | $pwa->domain = $domain; 163 | $pwa->data = $data; 164 | $pwa->save(); 165 | 166 | return redirect(route('pwa'))->with('success', 'Pwa settings updated successfully.'); 167 | } 168 | 169 | /** 170 | * Remove the specified resource from storage. 171 | * 172 | * @param \CodexShaper\PWA\Model\Setting $Setting 173 | * 174 | * @throws \Exception 175 | * 176 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse 177 | */ 178 | public function destroy(Setting $Setting) 179 | { 180 | try { 181 | if (File::isDirectory(storage_path('app/public/pwa'))) { 182 | File::deleteDirectory(storage_path('app/public/pwa')); 183 | } 184 | 185 | $pwa = $this->getPwaInstance(); 186 | 187 | if ($pwa) { 188 | $pwa->delete(); 189 | 190 | return redirect(route('pwa'))->with('success', 'Pwa deleted successfully.'); 191 | } 192 | } catch (Exception $ex) { 193 | throw new Exception($ex->getMessage()); 194 | } 195 | } 196 | 197 | /** 198 | * Return manifest.josn content. 199 | * 200 | * @return \Illuminate\Http\Response 201 | */ 202 | public function manifest() 203 | { 204 | $pwa = $this->getPwaInstance(); 205 | $manifest = $pwa->data['manifest']; 206 | $manifestArr = static::prepareManifest($manifest); 207 | 208 | return response()->json($manifestArr); 209 | } 210 | 211 | /** 212 | * Display pwa offline resources. 213 | * 214 | * @return \Illuminate\Http\Response 215 | */ 216 | public function offline() 217 | { 218 | return view('pwa::offline'); 219 | } 220 | 221 | /** 222 | * Prepare manifest data. 223 | * 224 | * @param array $manifest 225 | * 226 | * @return array 227 | */ 228 | public static function prepareManifest($manifest) 229 | { 230 | $data = [ 231 | 'name' => $manifest['name'], 232 | 'short_name' => $manifest['short_name'], 233 | 'start_url' => asset($manifest['start_url']), 234 | 'display' => $manifest['display'], 235 | 'theme_color' => $manifest['theme_color'], 236 | 'background_color' => $manifest['background_color'], 237 | 'orientation' => $manifest['orientation'], 238 | 'status_bar' => $manifest['status_bar'], 239 | 'splash' => $manifest['splash'], 240 | ]; 241 | 242 | foreach ($manifest['icons'] as $size => $file) { 243 | $fileInfo = pathinfo($file['path']); 244 | $data['icons'][] = [ 245 | 'src' => $file['path'], 246 | 'type' => 'image/'.$fileInfo['extension'], 247 | 'sizes' => $size, 248 | 'purpose' => $file['purpose'], 249 | ]; 250 | } 251 | 252 | foreach ($manifest['shortcuts'] as $shortcut) { 253 | if (array_key_exists('icons', $shortcut)) { 254 | $fileInfo = pathinfo($shortcut['icons']['src']); 255 | $icon = [ 256 | 'src' => $shortcut['icons']['src'], 257 | 'type' => 'image/'.$fileInfo['extension'], 258 | 'purpose' => $shortcut['icons']['purpose'], 259 | ]; 260 | } else { 261 | $icon = []; 262 | } 263 | 264 | $data['shortcuts'][] = [ 265 | 'name' => trans($shortcut['name']), 266 | 'description' => trans($shortcut['description']), 267 | 'url' => $shortcut['url'], 268 | 'icons' => [ 269 | $icon, 270 | ], 271 | ]; 272 | } 273 | 274 | foreach ($manifest['custom'] as $tag => $value) { 275 | $data[$tag] = $value; 276 | } 277 | 278 | return $data; 279 | } 280 | 281 | /** 282 | * Prepare manifest data from request for database. 283 | * 284 | * @param array $data 285 | * 286 | * @return array 287 | */ 288 | protected function getManifestData($data) 289 | { 290 | return [ 291 | 'name' => $data['name'], 292 | 'manifest' => [ 293 | 'name' => $data['name'], 294 | 'short_name' => $data['short_name'], 295 | 'start_url' => $data['start_url'], 296 | 'background_color' => $data['background_color'], 297 | 'theme_color' => $data['theme_color'], 298 | 'display' => $data['display'], 299 | 'orientation' => 'any', 300 | 'status_bar' => 'black', 301 | 'icons' => [ 302 | '72x72' => [ 303 | 'path' => pwa_asset('images/icons/icon-72x72.png'), 304 | 'purpose' => 'any', 305 | ], 306 | '96x96' => [ 307 | 'path' => pwa_asset('images/icons/icon-96x96.png'), 308 | 'purpose' => 'any', 309 | ], 310 | '128x128' => [ 311 | 'path' => pwa_asset('images/icons/icon-128x128.png'), 312 | 'purpose' => 'any', 313 | ], 314 | '144x144' => [ 315 | 'path' => pwa_asset('images/icons/icon-144x144.png'), 316 | 'purpose' => 'any', 317 | ], 318 | '152x152' => [ 319 | 'path' => pwa_asset('images/icons/icon-152x152.png'), 320 | 'purpose' => 'any', 321 | ], 322 | '192x192' => [ 323 | 'path' => pwa_asset('images/icons/icon-192x192.png'), 324 | 'purpose' => 'any', 325 | ], 326 | '384x384' => [ 327 | 'path' => pwa_asset('images/icons/icon-384x384.png'), 328 | 'purpose' => 'any', 329 | ], 330 | '512x512' => [ 331 | 'path' => pwa_asset('images/icons/icon-512x512.png'), 332 | 'purpose' => 'any', 333 | ], 334 | ], 335 | 'splash' => [ 336 | '640x1136' => pwa_asset('images/icons/splash-640x1136.png'), 337 | '750x1334' => pwa_asset('images/icons/splash-750x1334.png'), 338 | '828x1792' => pwa_asset('images/icons/splash-828x1792.png'), 339 | '1125x2436' => pwa_asset('images/icons/splash-1125x2436.png'), 340 | '1242x2208' => pwa_asset('images/icons/splash-1242x2208.png'), 341 | '1242x2688' => pwa_asset('images/icons/splash-1242x2688.png'), 342 | '1536x2048' => pwa_asset('images/icons/splash-1536x2048.png'), 343 | '1668x2224' => pwa_asset('images/icons/splash-1668x2224.png'), 344 | '1668x2388' => pwa_asset('images/icons/splash-1668x2388.png'), 345 | '2048x2732' => pwa_asset('images/icons/splash-2048x2732.png'), 346 | ], 347 | 'shortcuts' => [], 348 | 'custom' => [], 349 | ], 350 | ]; 351 | } 352 | 353 | /** 354 | * Return serviceworker.js content. 355 | * 356 | * @return \Illuminate\Http\Response 357 | */ 358 | public function serviceWorker() 359 | { 360 | $pwa = $this->getPwaInstance(); 361 | 362 | if ($pwa) { 363 | $response = Response::make($pwa->data['serviceworker'], 200); 364 | $response->header('Content-Type', 'text/javascript'); 365 | $response->setSharedMaxAge(31536000); 366 | $response->setMaxAge(31536000); 367 | $response->setExpires(new \DateTime('+1 year')); 368 | 369 | return $response; 370 | } 371 | } 372 | 373 | /** 374 | * Return serviceworker register content. 375 | * 376 | * @return \Illuminate\Http\Response 377 | */ 378 | public function serviceWorkerRegisterContent() 379 | { 380 | $pwa = $this->getPwaInstance(); 381 | 382 | if ($pwa) { 383 | $response = Response::make($pwa->data['register_serviceworker'], 200); 384 | $response->header('Content-Type', 'text/javascript'); 385 | $response->setSharedMaxAge(31536000); 386 | $response->setMaxAge(31536000); 387 | $response->setExpires(new \DateTime('+1 year')); 388 | 389 | return $response; 390 | } 391 | } 392 | 393 | /** 394 | * Generate service worker. 395 | * 396 | * @return string 397 | */ 398 | protected function generateServiceWorker() 399 | { 400 | $public_path = asset('/'); 401 | $pwa_asset = pwa_asset(''); 402 | $base_url = url('/'); 403 | 404 | return << { 422 | this.skipWaiting(); 423 | event.waitUntil( 424 | caches.open(staticCacheName) 425 | .then(cache => { 426 | return cache.addAll(filesToCache); 427 | }) 428 | ) 429 | }); 430 | 431 | // Clear cache on activate 432 | self.addEventListener('activate', event => { 433 | event.waitUntil( 434 | caches.keys().then(cacheNames => { 435 | return Promise.all( 436 | cacheNames 437 | .filter(cacheName => (cacheName.startsWith("pwa-"))) 438 | .filter(cacheName => (cacheName !== staticCacheName)) 439 | .map(cacheName => caches.delete(cacheName)) 440 | ); 441 | }) 442 | ); 443 | }); 444 | 445 | // Serve from Cache 446 | self.addEventListener("fetch", event => { 447 | event.respondWith( 448 | caches.match(event.request) 449 | .then(response => { 450 | return response || fetch(event.request); 451 | }) 452 | .catch(() => { 453 | return caches.match('offline'); 454 | }) 455 | ) 456 | }); 457 | SERVICE_WORKER; 458 | } 459 | 460 | /** 461 | * Register service worker. 462 | * 463 | * @return string 464 | */ 465 | protected function generateServiceWorkerRegister() 466 | { 467 | $serviceworker_route = route('pwa.serviceworker'); 468 | $scope = config('pwa.scope', '.'); 469 | 470 | return << 'array', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /src/PWA.php: -------------------------------------------------------------------------------- 1 | header('Content-Type', $mimeType); 41 | $response->setSharedMaxAge(31536000); 42 | $response->setMaxAge(31536000); 43 | $response->setExpires(new \DateTime('+1 year')); 44 | 45 | return $response; 46 | } 47 | 48 | return response('', 404); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/PwaServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/../database/migrations'); 19 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'pwa'); 20 | } 21 | 22 | /** 23 | * Register the service provider. 24 | * 25 | * @return void 26 | */ 27 | public function register() 28 | { 29 | $this->app->singleton('pwa', function () { 30 | return new PWA(); 31 | }); 32 | $this->mergeConfigFrom( 33 | __DIR__.'/../config/pwa.php', 34 | 'config' 35 | ); 36 | $this->loadHelpers(); 37 | $this->registerBladeDirectives(); 38 | $this->registerPublish(); 39 | $this->registerCommands(); 40 | } 41 | 42 | /** 43 | * Register blade directories. 44 | * 45 | * @return void 46 | */ 47 | protected function registerBladeDirectives() 48 | { 49 | $this->app->afterResolving('blade.compiler', function (BladeCompiler $blade) { 50 | $blade->directive('PWA', function () { 51 | return "render();?>"; 52 | }); 53 | }); 54 | } 55 | 56 | /** 57 | * Load all helpers. 58 | * 59 | * @return void 60 | */ 61 | protected function loadHelpers() 62 | { 63 | foreach (glob(__DIR__.'/Helpers/*.php') as $filename) { 64 | require_once $filename; 65 | } 66 | } 67 | 68 | /** 69 | * Register publishable assets. 70 | * 71 | * @return void 72 | */ 73 | protected function registerPublish() 74 | { 75 | $publishable = [ 76 | 'pwa.config' => [ 77 | __DIR__.'/../config/pwa.php' => config_path('pwa.php'), 78 | ], 79 | 'pwa.migrations' => [ 80 | __DIR__.'/../database/migrations/' => database_path('migrations'), 81 | ], 82 | 'pwa.tenant.migrations' => [ 83 | __DIR__.'/../database/migrations/' => database_path('migrations/tenant'), 84 | ], 85 | 'pwa.seeds' => [ 86 | __DIR__.'/../database/seeds/' => database_path('seeds'), 87 | ], 88 | 'pwa.views' => [ 89 | __DIR__.'/../resources/views' => resource_path('views/vendor/pwa'), 90 | ], 91 | 'pwa.resources' => [ 92 | __DIR__.'/../resources' => resource_path('views/vendor/pwa'), 93 | ], 94 | 'pwa.lang' => [ 95 | __DIR__.'/../resources/lang' => resource_path('lang'), 96 | ], 97 | ]; 98 | 99 | foreach ($publishable as $group => $paths) { 100 | $this->publishes($paths, $group); 101 | } 102 | } 103 | 104 | /** 105 | * Register commands. 106 | * 107 | * @return void 108 | */ 109 | private function registerCommands() 110 | { 111 | $this->commands(InstallPwa::class); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/PWA.php: -------------------------------------------------------------------------------- 1 | assertSame(true, true); 13 | } 14 | } 15 | --------------------------------------------------------------------------------