- All Implemented Interfaces:
FacesWrapper<ResourceHandler>
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
- Create a class which extends
WebAppManifestin your web application project. - Give it the appropriate CDI scope annotation, e.g
ApplicationScoped,SessionScopedor evenRequestScoped(note that aViewScopedwon'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 of manifest.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 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.
For backwards compatibility with earlier OmniFaces versions, the resource name of manifest.json is also
supported. The default resource name has changed fo manifest.webmanifest since version 4.2 in order to
comply the new change in W3C spec.
The resource will use the application/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 your
web.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 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");
}
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 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:
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe resource namemanifest.webmanifest.static final StringThe resource namesw.js.Fields inherited from class org.omnifaces.resourcehandler.DefaultResourceHandler
RES_NOT_FOUNDFields inherited from class jakarta.faces.application.ResourceHandler
FACES_SCRIPT_LIBRARY_NAME, FACES_SCRIPT_RESOURCE_NAME, 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
ConstructorsConstructorDescriptionPWAResourceHandler(ResourceHandler wrapped) Creates a new instance of this web app manifest resource handler which wraps the given resource handler. -
Method Summary
Modifier and TypeMethodDescriptiondecorateResource(Resource resource, String resourceName, String libraryName) Decorate the given resource.static booleanisServiceWorkerRequest(FacesContext context) Returnstrueif the current request is triggered by a sw.js request.static booleanisServiceWorkerRequest(HttpServletRequest request) Returnstrueif the given request is triggered by a sw.js request.Methods inherited from class org.omnifaces.resourcehandler.DefaultResourceHandler
createResource, createResource, createResource, createResourceFromLibrary, decorateResource, getLibraryNameMethods inherited from class jakarta.faces.application.ResourceHandlerWrapper
createResourceFromId, createViewResource, getRendererTypeForResourceName, getViewResources, getViewResources, getWrapped, handleResourceRequest, isResourceRendered, isResourceRequest, isResourceURL, libraryExists, markResourceRendered
-
Field Details
-
MANIFEST_RESOURCE_NAME
The resource namemanifest.webmanifest.- See Also:
-
SERVICEWORKER_RESOURCE_NAME
The resource namesw.js.- See Also:
-
-
Constructor Details
-
PWAResourceHandler
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 Details
-
decorateResource
Description copied from class:DefaultResourceHandlerDecorate 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:
decorateResourcein classDefaultResourceHandler- Parameters:
resource- The resource to be decorated.resourceName- The resource name.libraryName- The library name.- Returns:
- The decorated resource.
-
isServiceWorkerRequest
Returnstrueif the current request is triggered by a sw.js request.- Parameters:
context- The involved faces context.- Returns:
trueif the current request is triggered by a sw.js request.- Since:
- 4.1
-
isServiceWorkerRequest
Returnstrueif the given request is triggered by a sw.js request.- Parameters:
request- The involved request.- Returns:
trueif the given request is triggered by a sw.js request.- Since:
- 4.1
-