├── .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 | [](http://badges.mit-license.org)
2 | [](https://travis-ci.org/Codexshaper/laravel-pwa)
3 | [](https://github.styleci.io/repos/279073965?branch=master)
4 | [](https://scrutinizer-ci.com/g/Codexshaper/laravel-pwa)
5 | [](https://packagist.org/packages/Codexshaper/laravel-pwa)
6 | [](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 | [](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 |
65 | @elseif(isset($pwa->data) && $pwa->status == 0)
66 |
70 |
75 | @elseif(isset($pwa->data) && $pwa->status == 1)
76 |
80 |
85 | {{-- PWA app information --}}
86 |
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 <<getHttpHost())->first();
496 | }
497 |
498 | /**
499 | * Return storage asset.
500 | *
501 | * @return \Illuminate\Http\Response
502 | */
503 | public function asset($path)
504 | {
505 | try {
506 | return response()->file(storage_path("app/public/pwa/$path"));
507 | } catch (\Throwable $th) {
508 | abort(404);
509 | }
510 | }
511 | }
512 |
--------------------------------------------------------------------------------
/src/Model/Setting.php:
--------------------------------------------------------------------------------
1 | '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 |
--------------------------------------------------------------------------------