Class PWAResourceHandler

  • All Implemented Interfaces:
    FacesWrapper<ResourceHandler>

    public class PWAResourceHandler
    extends DefaultResourceHandler

    This ResourceHandler generates the manifest.json and also an offline-aware sw.js based on any WebAppManifest found in the runtime classpath. Historical note: this class was introduced in 3.6 as WebAppManifestResourceHandler without any service worker related logic and since 3.7 renamed to PWAResourceHandler after having service worker related logic added.

    Usage

    1. Create a class which extends WebAppManifest in your web application project.
    2. Give it the appropriate CDI scope annotation, e.g ApplicationScoped, SessionScoped or even RequestScoped (note that a ViewScoped won't work).
    3. Override properties accordingly conform javadoc and the rules in the W3 spec.
    4. Reference it as #{resource['omnifaces:manifest.json']} in your template.

    Here's a concrete example:

     package com.example;
    
     import java.util.Arrays;
     import java.util.Collection;
     import jakarta.enterprise.context.ApplicationScoped;
    
     import org.omnifaces.resourcehandler.WebAppManifest;
    
     @ApplicationScoped
     public class ExampleWebAppManifest extends WebAppManifest {
    
         @Override
         public String getName() {
             return "Example Application Name";
         }
    
         @Override
         public String getShortName() {
             return "EAN";
         }
    
         @Override
         public Collection<ImageResource> getIcons() {
             return Arrays.asList(
                 ImageResource.of("logo.svg"),
                 ImageResource.of("logo-120x120.png", Size.SIZE_120),
                 ImageResource.of("logo-180x180.png", Size.SIZE_180),
                 ImageResource.of("logo-192x192.png", Size.SIZE_192),
                 ImageResource.of("logo-512x512.png", Size.SIZE_512)
             );
         }
    
         @Override
         public String getThemeColor() {
             return "#cc9900";
         }
    
         @Override
         public String getBackgroundColor() {
             return "#ffffff";
         }
    
         @Override
         public Display getDisplay() {
             return Display.STANDALONE;
         }
    
         @Override
         public Collection<Category> getCategories() {
             return Arrays.asList(Category.BUSINESS, Category.FINANCE);
         }
    
         @Override
         public Collection<RelatedApplication> getRelatedApplications() {
             return Arrays.asList(
                 RelatedApplication.of(Platform.PLAY, "https://play.google.com/store/apps/details?id=com.example.app1", "com.example.app1"),
                 RelatedApplication.of(Platform.ITUNES, "https://itunes.apple.com/app/example-app1/id123456789")
             );
         }
     }
     

    Reference it in your template exactly as follows, with the exact library name of omnifaces and exact resource name of manifest.json. You cannot change these values.

     <link rel="manifest" href="#{resource['omnifaces:manifest.json']}" crossorigin="use-credentials" />
     

    The crossorigin attribute is optional, you can drop it, but it's mandatory if you've put the SessionScoped annotation on your WebAppManifest bean, else the browser won't retain the session cookies while downloading the manifest.json and then this resource handler won't be able to maintain the server side cache, see also next section.

    Note: you do not need to explicitly register this resource handler. It's already automatically registered.

    Server side caching

    Basically, the CDI scope annotation being used is determinative for the autogenerated v= query parameter indicating the last modified timestamp. If you make your WebAppManifest bean RequestScoped, then it'll change on every request and the browser will be forced to re-download it. If you can however guarantee that the properties of your WebAppManifest are static, and thus you can safely make it ApplicationScoped, then the v= query parameter will basically represent the timestamp of the first time the bean is instantiated.

    Offline-aware service worker

    The generated sw.js will by default auto-register the WebAppManifest.getStartUrl() and all welcome files from web.xml as cacheable resources which are also available offline. You can override the welcome files with WebAppManifest.getCacheableViewIds(). E.g.

     @Override
     public Collection<String> getCacheableViewIds() {
         return Arrays.asList("/index.xhtml", "/contact.xhtml", "/support.xhtml");
     }
     

    In case you want to show a custom page as "You are offline!" error page, then you can specify it by overriding the WebAppManifest.getOfflineViewId().

     @Override
     public String getOfflineViewId() {
         return "/offline.xhtml";
     }
     

    Whereby the offline.xhtml should contain something like this:

     <h1>Whoops! You appear to be offline!</h1>
     <p>Please check your connection and then try refreshing this page.</p>
     

    For each of those "cacheable view IDs" and "offline view IDs", the Faces view briefly will be built in in order to extract all <x:outputStylesheet>,<x:outputScript> and <x:graphicImage> resources and add them to cacheable resources of the service worker as well.

    If the WebAppManifest.getCacheableViewIds() returns an empty collection, then no sw.js will be generated, and WebAppManifest.getOfflineViewId() will also be ignored.

    Client side events

    In the client side, you can listen on omnifaces.offline and omnifaces.online events in the window whether the client is currently online or offline.

     window.addEventListener("omnifaces.online", function(event) {
         var url = event.detail.url;
         // ..
     });
     window.addEventListener("omnifaces.offline", function(event) {
         var url = event.detail.url;
         var error = event.detail.error;
         // ...
     });
     

    Or when you're using jQuery:

     $(window).on("omnifaces.online", function(event) {
         var url = event.detail.url;
         // ..
     });
     $(window).on("omnifaces.offline", function(event) {
         var url = event.detail.url;
         var error = event.detail.error;
         // ...
     });
     

    This gives you the opportunity to set a global flag and/or show some sort of notification. The event.detail will contain at least the url which was being requested through the service worker, and in case of the omnifaces.offline event, there will also be an error which represents the original network error object thrown by fetch().

    Since:
    3.7
    Author:
    Bauke Scholtz
    See Also:
    WebAppManifest, https://www.w3.org/TR/appmanifest, https://developer.mozilla.org/en-US/docs/Web/Manifest, https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
    • Field Detail

      • MANIFEST_RESOURCE_NAME

        public static final String MANIFEST_RESOURCE_NAME
        The resource name manifest.json.
        See Also:
        Constant Field Values
      • SERVICEWORKER_RESOURCE_NAME

        public static final String SERVICEWORKER_RESOURCE_NAME
        The resource name sw.js.
        See Also:
        Constant Field Values
    • Constructor Detail

      • PWAResourceHandler

        public PWAResourceHandler​(ResourceHandler wrapped)
        Creates a new instance of this web app manifest resource handler which wraps the given resource handler. This will also try to resolve the concrete implementation of WebAppManifest.
        Parameters:
        wrapped - The resource handler to be wrapped.
    • Method Detail

      • isServiceWorkerRequest

        public static boolean isServiceWorkerRequest​(FacesContext context)
        Returns true if the current request is triggered by a sw.js request.
        Parameters:
        context - The involved faces context.
        Returns:
        true if the current request is triggered by a sw.js request.
        Since:
        4.1
      • isServiceWorkerRequest

        public static boolean isServiceWorkerRequest​(HttpServletRequest request)
        Returns true if the given request is triggered by a sw.js request.
        Parameters:
        request - The involved request.
        Returns:
        true if the given request is triggered by a sw.js request.
        Since:
        4.1