- java.lang.Object
-
- jakarta.faces.application.ResourceHandler
-
- jakarta.faces.application.ResourceHandlerWrapper
-
- org.omnifaces.resourcehandler.DefaultResourceHandler
-
- org.omnifaces.resourcehandler.PWAResourceHandler
-
- All Implemented Interfaces:
FacesWrapper<ResourceHandler>
public class PWAResourceHandler extends DefaultResourceHandler
This
ResourceHandler
generates themanifest.json
and also an offline-awaresw.js
based on anyWebAppManifest
found in the runtime classpath. Historical note: this class was introduced in 3.6 asWebAppManifestResourceHandler
without any service worker related logic and since 3.7 renamed toPWAResourceHandler
after having service worker related logic added.Usage
- Create a class which extends
WebAppManifest
in your web application project. - Give it the appropriate CDI scope annotation, e.g
ApplicationScoped
,SessionScoped
or evenRequestScoped
(note that aViewScoped
won't work). - Override properties accordingly conform javadoc and the rules in the W3 spec.
- Reference it as
#{resource['omnifaces:manifest.webmanifest']}
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 ofmanifest.webmanifest
. You cannot change these values.<link rel="manifest" href="#{resource['omnifaces:manifest.webmanifest']}" crossorigin="use-credentials" />
The
crossorigin
attribute is optional, you can drop it, but it's mandatory if you've put theSessionScoped
annotation on yourWebAppManifest
bean, else the browser won't retain the session cookies while downloading themanifest.json
and then this resource handler won't be able to maintain the server side cache, see also next section.For backwards compatibility with earlier OmniFaces versions, the resource name of
manifest.json
is also supported. The default resource name has changed fomanifest.webmanifest
since version 4.2 in order to comply the new change in W3C spec. The resource will use theapplication/manifest+json
content type. In case you face the following warning in server logs or something similar,WARNING: JSF1091: No mime type could be found for file manifest.webmanifest. To resolve this, add a mime-type mapping to the applications web.xml.
then take action accordingly by adding the following entry to yourweb.xml
:<mime-mapping> <extension>webmanifest</extension> <mime-type>application/manifest+json</mime-type> </mime-mapping>
Note: you do not need to explicitly register this resource handler in your
faces-config.xml
. 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 yourWebAppManifest
beanRequestScoped
, 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 yourWebAppManifest
are static, and thus you can safely make itApplicationScoped
, then thev=
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 theWebAppManifest.getStartUrl()
and all welcome files fromweb.xml
as cacheable resources which are also available offline. You can override the welcome files withWebAppManifest.getCacheableViewIds()
. E.g.@Override public Collection<String> getCacheableViewIds() { return Arrays.asList("/index.xhtml", "/contact.xhtml", "/support.xhtml"); }
If this method returns an empty collection, i.e. there are no cacheable resources at all, and thus also no offline resources at all, then no service worker file will be generated as it won't have any use then.
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 nosw.js
will be generated, andWebAppManifest.getOfflineViewId()
will also be ignored.Client side events
In the client side, you can listen on
omnifaces.offline
andomnifaces.online
events in thewindow
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 theurl
which was being requested through the service worker, and in case of theomnifaces.offline
event, there will also be anerror
which represents the original network error object thrown byfetch()
.- 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 Summary
Fields Modifier and Type Field Description static String
MANIFEST_RESOURCE_NAME
The resource namemanifest.webmanifest
.static String
SERVICEWORKER_RESOURCE_NAME
The resource namesw.js
.-
Fields inherited from class org.omnifaces.resourcehandler.DefaultResourceHandler
FACES_SCRIPT_RESOURCE_NAME, RES_NOT_FOUND
-
Fields inherited from class jakarta.faces.application.ResourceHandler
JSF_SCRIPT_LIBRARY_NAME, JSF_SCRIPT_RESOURCE_NAME, LOCALE_PREFIX, RESOURCE_CONTRACT_XML, RESOURCE_EXCLUDES_DEFAULT_VALUE, RESOURCE_EXCLUDES_PARAM_NAME, RESOURCE_IDENTIFIER, WEBAPP_CONTRACTS_DIRECTORY_PARAM_NAME, WEBAPP_RESOURCES_DIRECTORY_PARAM_NAME
-
-
Constructor Summary
Constructors Constructor Description PWAResourceHandler(ResourceHandler wrapped)
Creates a new instance of this web app manifest resource handler which wraps the given resource handler.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description Resource
decorateResource(Resource resource, String resourceName, String libraryName)
Decorate the given resource.static boolean
isServiceWorkerRequest(FacesContext context)
Returnstrue
if the current request is triggered by a sw.js request.static boolean
isServiceWorkerRequest(HttpServletRequest request)
Returnstrue
if the given request is triggered by a sw.js request.-
Methods inherited from class org.omnifaces.resourcehandler.DefaultResourceHandler
createResource, createResource, createResource, createResourceFromLibrary, decorateResource, getLibraryName
-
Methods inherited from class jakarta.faces.application.ResourceHandlerWrapper
createResourceFromId, createViewResource, getRendererTypeForResourceName, getViewResources, getViewResources, getWrapped, handleResourceRequest, isResourceRendered, isResourceRequest, isResourceURL, libraryExists, markResourceRendered
-
-
-
-
Field Detail
-
MANIFEST_RESOURCE_NAME
public static final String MANIFEST_RESOURCE_NAME
The resource namemanifest.webmanifest
.- See Also:
- Constant Field Values
-
SERVICEWORKER_RESOURCE_NAME
public static final String SERVICEWORKER_RESOURCE_NAME
The resource namesw.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 ofWebAppManifest
.- Parameters:
wrapped
- The resource handler to be wrapped.
-
-
Method Detail
-
decorateResource
public Resource decorateResource(Resource resource, String resourceName, String libraryName)
Description copied from class:DefaultResourceHandler
Decorate the given resource. This will only be called if no library-specific resource has been requested.The default implementation delegates to
DefaultResourceHandler.decorateResource(Resource)
.- Overrides:
decorateResource
in classDefaultResourceHandler
- Parameters:
resource
- The resource to be decorated.resourceName
- The resource name.libraryName
- The library name.- Returns:
- The decorated resource.
-
isServiceWorkerRequest
public static boolean isServiceWorkerRequest(FacesContext context)
Returnstrue
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)
Returnstrue
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
-
-