From 914af569a2d1aa737955649a650b96b4e7d6a8f0 Mon Sep 17 00:00:00 2001 From: Sven Nolting Date: Sat, 6 Nov 2021 20:47:25 +0100 Subject: [PATCH] Serviceworker einrichten (#227) * Create draft PR for #226 * Basic Serviceworker * Kleinere Anpassungen * Serviceworker version --- .ddev/config.yaml | 50 +++++++++++++++-------- .env | 2 +- .github/workflows/ci.yml | 6 ++- .github/workflows/codeql-analysis.yml | 2 +- assets/js/scripts.js | 45 +++++++++++++++++++- src/Controller/PwaController.php | 47 ++++++++++++++++++++- src/Service/UtilityService.php | 36 ++++++++++++++++ templates/base.html.twig | 2 +- templates/order/index.html.twig | 2 +- templates/order/paynow.html.twig | 2 +- templates/serviceworker.js | 59 +++++++++++++++++++++++++++ 11 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 src/Service/UtilityService.php create mode 100644 templates/serviceworker.js diff --git a/.ddev/config.yaml b/.ddev/config.yaml index b67529f21..1257f29ec 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -10,22 +10,14 @@ additional_hostnames: [] additional_fqdns: [] mariadb_version: "10.3" mysql_version: "" -use_dns_when_possible: true -composer_version: "" -web_environment: [] -webimage_extra_packages: [php8.0-imap] +nfs_mount_enabled: false +mutagen_enabled: false hooks: post-start: - - composer: install --no-interaction - - -# This config.yaml was created with ddev version v1.17.1 -# webimage: drud/ddev-webserver:v1.17.1 -# dbimage: drud/ddev-dbserver-mariadb-10.3:v1.17.0 -# dbaimage: phpmyadmin:5 -# However we do not recommend explicitly wiring these images into the -# config.yaml as they may break future versions of ddev. -# You can update this config.yaml using 'ddev config'. + - composer: install --no-interaction +webimage_extra_packages: [php8.0-imap] +use_dns_when_possible: true +composer_version: "" # Key features of ddev's config.yaml: @@ -60,6 +52,11 @@ hooks: # "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better, # as leaving xdebug enabled all the time is a big performance hit. +# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better, +# as leaving xhprof enabled all the time is a big performance hit. + # webserver_type: nginx-fpm # or apache-fpm # timezone: Europe/Berlin @@ -101,12 +98,17 @@ hooks: # Currently only these containers are supported. Some containers can also be # omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit # the "db" container, several standard features of ddev that access the -# database container will be unusable. +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. # nfs_mount_enabled: false # Great performance improvement but requires host configuration first. # See https://ddev.readthedocs.io/en/stable/users/performance/#using-nfs-to-mount-the-project-into-the-container +# mutagen_enabled: false +# Experimental performance improvement using mutagen asynchronous updates. +# See https://ddev.readthedocs.io/en/latest/users/performance/#using-mutagen + # fail_on_hook_fail: False # Decide whether 'ddev start' should be interrupted by a failing hook @@ -130,10 +132,18 @@ hooks: # phpmyadmin_https_port: "8037" # The PHPMyAdmin ports can be changed from the default 8036 and 8037 +# host_phpmyadmin_port: "8036" +# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be specified and bound. + # mailhog_port: "8025" # mailhog_https_port: "8026" # The MailHog ports can be changed from the default 8025 and 8026 +# host_mailhog_port: "8025" +# The mailhog port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + # webimage_extra_packages: [php7.4-tidy, php-bcmath] # Extra Debian packages that are needed in the webimage can be added here @@ -162,7 +172,7 @@ hooks: # In this case the user must provide all such settings. # You can inject environment variables into the web container with: -# web_environment: +# web_environment: # - SOMEENV=somevalue # - SOMEOTHERENV=someothervalue @@ -172,8 +182,12 @@ hooks: # This is to enable experimentation with alternate file mounting strategies. # For advanced users only! -# provider: default # Currently "default", "pantheon", "ddev-live" -# +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not just the localhost interface. This means that ports +# will be available on the local network if the host firewall +# allows it. + # Many ddev commands can be extended to run tasks before or after the # ddev command is executed, for example "post-start", "post-import-db", # "pre-composer", "post-composer" diff --git a/.env b/.env index 34f283bec..50675e111 100644 --- a/.env +++ b/.env @@ -32,4 +32,4 @@ DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=mariadb-10.3.31" ###< doctrine/doctrine-bundle ### MailAccess_host="{sslin.df.eu/imap/ssl}INBOX" MailAccess_username="essen@hochwarth-e.com" -MailAccess_password="Foodwars#1" +MailAccess_password="" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a73d03f2e..1218e063e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,9 @@ jobs: echo 'EMAILPASS="${{ secrets.EMAILPASS }}"' >> .env.local echo 'EMAILUSER="essen@hochwarth-e.com"' >> .env.local echo 'POBOX="{sslin.df.eu/imap/ssl}INBOX/"' >> .env.local - echo 'SWVERSION="${{ steps.get_version.outputs.VERSION }}"' >> .env.local + echo 'APP_VERSION="${{ steps.get_version.outputs.VERSION }}"' >> .env.local + echo 'HT_USERNAME="${{ secrets.HT_USER }}"' >> .env.local + echo 'HT_PASSWORD="${{ secrets.HT_PASS }}"' >> .env.local composer dump-env prod - name: Passwortschutz erstellen run: | @@ -135,7 +137,7 @@ jobs: run: rm artifact.tar - run: sudo apt-get install -y sshpass rsync name: 📦 Installing upload dependencies - - run: sshpass -p '${{ secrets.PASSWORD }}' rsync --exclude ".git" --exclude ".github" -e "ssh -o StrictHostKeyChecking=no -p 222" -rltzvOP --del --force ./ ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/usr/home/${{ secrets.USERNAME }}/public_html/hirsch + - run: sshpass -p '${{ secrets.PASSWORD }}' rsync --exclude ".git" --exclude ".ddev" --exclude ".github" -e "ssh -o StrictHostKeyChecking=no -p 222" -rltzvOP --del --force ./ ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/usr/home/${{ secrets.USERNAME }}/public_html/hirsch id: upload name: 👨‍💻 Uploading files - run: sshpass -p '${{ secrets.PASSWORD }}' ssh -o StrictHostKeyChecking=no ${{ secrets.USERNAME }}@${{ secrets.HOST }} -p 222 -f "cd /usr/home/${{ secrets.USERNAME }}/public_html/hirsch && bin/console --no-interaction doctrine:migrations:migrate && bin/console --no-interaction cache:clear" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 91824cdd4..6c6f03356 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -80,7 +80,7 @@ jobs: echo 'EMAILPASS="${{ secrets.EMAILPASS }}"' >> .env.local echo 'EMAILUSER="essen@hochwarth-e.com"' >> .env.local echo 'POBOX="{sslin.df.eu/imap/ssl}INBOX/"' >> .env.local - echo 'SWVERSION="${{ steps.get_version.outputs.VERSION }}"' >> .env.local + echo 'APP_VERSION="${{ steps.get_version.outputs.VERSION }}"' >> .env.local composer dump-env prod - name: Passwortschutz erstellen run: | diff --git a/assets/js/scripts.js b/assets/js/scripts.js index c68e4c6ee..eebef0810 100644 --- a/assets/js/scripts.js +++ b/assets/js/scripts.js @@ -117,4 +117,47 @@ $(document).ready(() => { }); // fire input event on class range-slider__range to update value $(".range-slider__range").trigger("input"); -}); \ No newline at end of file +}); + +// check if the browser supports serviceWorker at all +if ('serviceWorker' in navigator) { + // wait for the page to load + window.addEventListener('load', async() => { + // register the service worker from the file specified + const registration = await navigator.serviceWorker.register('/sw.js') + + // ensure the case when the updatefound event was missed is also handled + // by re-invoking the prompt when there's a waiting Service Worker + if (registration.waiting) { + registration.waiting.postMessage('SKIP_WAITING') + } + + // detect Service Worker update available and wait for it to become installed + registration.addEventListener('updatefound', () => { + if (registration.installing) { + // wait until the new Service worker is actually installed (ready to take over) + registration.installing.addEventListener('statechange', () => { + if (registration.waiting) { + // if there's an existing controller (previous Service Worker), send update message + if (navigator.serviceWorker.controller) { + registration.waiting.postMessage('SKIP_WAITING') + } else { + // otherwise it's the first install, nothing to do + console.log('Service Worker initialized for the first time') + } + } + }) + } + }) + + let refreshing = false; + + // detect controller change and refresh the page + navigator.serviceWorker.addEventListener('controllerchange', () => { + if (!refreshing) { + window.location.reload() + refreshing = true + } + }) + }) +} \ No newline at end of file diff --git a/src/Controller/PwaController.php b/src/Controller/PwaController.php index cd618ad1f..7567ca0ac 100644 --- a/src/Controller/PwaController.php +++ b/src/Controller/PwaController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Service\UtilityService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -12,7 +13,7 @@ class PwaController extends AbstractController /** * @Route("/manifest.json") */ - public function index(): JsonResponse + public function manifest(): JsonResponse { return new JsonResponse([ "lang" => "de-DE", @@ -32,4 +33,48 @@ public function index(): JsonResponse "orientation" => "portrait" ], 200, ['Content-Type' => 'application/manifest+json']); } + + /** + * @Route("/sw.js") + */ + public function serviceWorker(UtilityService $utilityService): Response + { + + // read /public/build/manifest.json and parse it + $manifest = json_decode(file_get_contents(__DIR__ . '/../../public/build/manifest.json'), true); + + $urlsToCache = [ + '/karte', + '/favicon.png', + 'https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js', + 'https://cdn.jsdelivr.net/npm/flatpickr', + 'https://fonts.googleapis.com/icon?family=Material+Icons', + 'https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css', + 'https://fonts.googleapis.com/css?family=Raleway:400,700', + 'https://fonts.gstatic.com/s/raleway/v22/1Ptug8zYS_SKggPNyCAIT5lu.woff2', + 'https://fonts.gstatic.com/s/raleway/v22/1Ptug8zYS_SKggPNyCkIT5lu.woff2', + 'https://fonts.gstatic.com/s/raleway/v22/1Ptug8zYS_SKggPNyCIIT5lu.woff2', + 'https://fonts.gstatic.com/s/raleway/v22/1Ptug8zYS_SKggPNyCMIT5lu.woff2', + 'https://fonts.gstatic.com/s/raleway/v22/1Ptug8zYS_SKggPNyC0ITw.woff2', + ]; + + // merge $manifest and $urlsToCache + $urlsToCache = array_merge(array_values($manifest), $urlsToCache); + + $response = new Response( + null, + 200, + ['Content-Type' => 'application/javascript'] + ); + + return $this->render('serviceworker.js', [ + 'version' => $_ENV['APP_VERSION'] ?? $utilityService->hashDirectory(__DIR__."/../../public/build") ?? '0.0.0', + 'urlsToCache' => $urlsToCache, + 'credentials' => [ + 'username' => $_ENV['HT_USERNAME'] ?? '', + 'password' => $_ENV['HT_PASSWORD'] ?? '', + 'string' => base64_encode(($_ENV['HT_USERNAME'] ?? '') . ':' . ($_ENV['HT_PASSWORD'] ?? '')), + ] + ], $response); + } } diff --git a/src/Service/UtilityService.php b/src/Service/UtilityService.php new file mode 100644 index 000000000..b27a41ff6 --- /dev/null +++ b/src/Service/UtilityService.php @@ -0,0 +1,36 @@ +read())) { + if ($file != '.' and $file != '..') { + if (is_dir($directory . '/' . $file)) { + $files[] = $this->hashDirectory($directory . '/' . $file); + } else { + $files[] = md5_file($directory . '/' . $file); + } + } + } + + $dir->close(); + + return md5(implode('', $files)); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index 3723f8fbf..f112cc473 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -11,7 +11,7 @@ - {% block title %}Welcome!{% endblock %} + {% block title %}?{% endblock %} | Hirschbestellung {% block stylesheets %} {{ encore_entry_link_tags('app') }} diff --git a/templates/order/index.html.twig b/templates/order/index.html.twig index bb4ad6045..1adb5c712 100644 --- a/templates/order/index.html.twig +++ b/templates/order/index.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% block title %}Hello OrderController!{% endblock %} +{% block title %}Bestellen{% endblock %} {% block body %}

{{meal.name}}

diff --git a/templates/order/paynow.html.twig b/templates/order/paynow.html.twig index ecf6fc407..a9c5391e5 100644 --- a/templates/order/paynow.html.twig +++ b/templates/order/paynow.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% block title %}Zahlerliste{% endblock %} +{% block title %}Bezahlen{% endblock %} {% block body %}

Paypalierer

diff --git a/templates/serviceworker.js b/templates/serviceworker.js new file mode 100644 index 000000000..280ffde3c --- /dev/null +++ b/templates/serviceworker.js @@ -0,0 +1,59 @@ +var CACHE_NAME = '{{ version }}'; +var urlsToCache = {{ urlsToCache | json_encode(constant('JSON_UNESCAPED_SLASHES')) | raw }}; +self.addEventListener('install', function(event) { + event.waitUntil( + caches.open(CACHE_NAME) + .then(function(cache) { + console.log('Opened cache ' + CACHE_NAME); + return cache.addAll(urlsToCache); + }) + ); +}); +self.addEventListener('activate', function(event) { + event.waitUntil( + caches.keys().then(keys => { + return Promise.all(keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))) + }) + ) +}); +self.addEventListener('fetch', function(event) { + if (event.request.method === "GET") { + event.respondWith( + caches.match(event.request) + .then(function(response) { + var headers = {}; + if (event.request.url.startsWith("https://hirsch.hochwarth-e.com/")) { + Object.assign(headers, { Authorization: 'Basic {{ credentials.string | raw }}' }); + } + if (event.request.url.includes("get-")) { + Object.assign(headers, { Accept: 'application/json' }); + } + + const request = new Request(event.request, { headers }); + // Cache hit - return response + return response || fetch(request).catch(() => { + let init; + if (event.request.url.includes("get-tagesessen")) { + init = {"status": 200, "statusText": "Dummy"}; + return new Response('{"displayData": [], "file": ""}', init); + } + if (event.request.url.includes("modalInformationText")) { + init = { "status": 418, "statusText": "I am a Teapot" }; + return new Response("Du bist aktuell offline! Die angezeigten Daten sind unter Umständen nicht aktuell!", init); + } + if (event.request.url.includes("order-until")) { + init = { "status": 200, "statusText": "Offline" }; + return new Response("Du bist aktuell offline!", init); + } + return caches.match("/fallback.html") + }); + }) + ); + } +}); + +self.addEventListener('message', (event) => { + if (event.data === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); \ No newline at end of file