Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions vaadin-touchkit-agpl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<version>1.0.0</version>
<executions>
<execution>
<phase>initialize</phase>
Expand All @@ -34,6 +34,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down Expand Up @@ -63,6 +64,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<!-- Prevent DocLint from cancelling the build -->
<configuration>
<failOnError>false</failOnError>
Expand Down Expand Up @@ -157,8 +159,8 @@
<!-- May be overridden by the build system -->
<license.short.name>agpl</license.short.name>
<license.version>3.0</license.version>
<vaadin.version.maven>[7.6.0.beta1,7.99.9999]</vaadin.version.maven>
<vaadin.plugin.version>7.6.0.beta1</vaadin.plugin.version>
<vaadin.version.maven>7.6.0</vaadin.version.maven>
<vaadin.plugin.version>7.6.0</vaadin.plugin.version>
<gpg.passphrase.file>empty.properties</gpg.passphrase.file>
<gpg.skip>true</gpg.skip>
<snapshot.repository.url>http://oss.sonatype.org/content/repositories/vaadin-snapshots/</snapshot.repository.url>
Expand Down Expand Up @@ -253,8 +255,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import static com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode.ActivationReason.BAD_RESPONSE;
import static com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode.ActivationReason.SERVER_AVAILABLE;

import com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineModeEntrypoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.Response;
import com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineModeEntrypoint;
import com.google.gwt.user.client.Window;
import com.vaadin.client.communication.ConnectionStateHandler;
import com.vaadin.client.communication.DefaultConnectionStateHandler;
import com.vaadin.client.communication.PushConnection;
Expand Down Expand Up @@ -40,6 +40,13 @@ public void heartbeatException(Request request, Throwable exception) {

@Override
public void heartbeatInvalidStatusCode(Request request, Response response) {
if(response.getStatusCode() == 410) {
// This has broken at some point, session expired don't properly
// restart the application. Do a manual reload as I don't know how
// it should be done these days...
// TODO ask somebody who knows about this mess
Window.Location.reload();
}
if (!offlineModeEntrypoint.isOfflineModeEnabled()) {
super.heartbeatInvalidStatusCode(request, response);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,10 @@ protected final native void hookAllListeners(
*/
private static native int getStatus()
/*-{
return $wnd.applicationCache.status;
if($wnd.applicationCache) {
return $wnd.applicationCache.status;
}
return 99;
}-*/;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected String generateManifestFileName(BootstrapPageResponse response) {
* enabled.
*/
public boolean isCacheManifestEnabled() {
return cacheManifestEnabled;
return cacheManifestEnabled && !TouchKitSettings.supportsGooglePWA();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,32 @@
import com.vaadin.server.BootstrapListener;
import com.vaadin.server.BootstrapPageResponse;
import com.vaadin.server.CustomizedSystemMessages;
import com.vaadin.server.RequestHandler;
import com.vaadin.server.ServiceException;
import com.vaadin.server.SessionInitEvent;
import com.vaadin.server.SessionInitListener;
import com.vaadin.server.SystemMessages;
import com.vaadin.server.SystemMessagesInfo;
import com.vaadin.server.SystemMessagesProvider;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServletService;
import com.vaadin.server.VaadinSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import com.vaadin.addon.touchkit.service.ApplicationIcon;

/**
* TouchKitSettings is a collection of tools that help modify various touch
Expand All @@ -31,15 +49,18 @@
public class TouchKitSettings implements BootstrapListener,
SessionInitListener, SystemMessagesProvider {

private List<String> stronglyCachedResources;

/**
* Interface to select different settings for different kind of devices
* based on e.g. HTTP header inspection.
*/
interface SettingSelector<T> extends Serializable {

/**
* @param response
* @return used settings or null if settings shouldn't be used for this
* request.
* request.
*/
T select(BootstrapPageResponse response);
}
Expand All @@ -63,8 +84,8 @@ public TouchKitSettings() {
* Creates a new instance of TouchKitSettings and binds it to the given
* {@link VaadinService}.
*
* @param vaadinService
* the vaadin service to which the new instance should be bound.
* @param vaadinService the vaadin service to which the new instance should
* be bound.
*/
public TouchKitSettings(VaadinService vaadinService) {
setViewPortSettings(new ViewPortSettings());
Expand Down Expand Up @@ -185,11 +206,175 @@ public void modifyBootstrapPage(BootstrapPageResponse response) {
offline == null || offline.value());
getApplicationCacheSettings().modifyBootstrapPage(response);
}

if (supportsGooglePWA()) {

String contextPath = response.getRequest().getContextPath();

Document document = response.getDocument();
// manifest.json
// <link rel="manifest" href="/manifest.json">
Element element = document.createElement("link");
element.attr("rel", "manifest");
element.attr("href", contextPath + "/manifest.json");
document.getElementsByTag("head").get(0).appendChild(element);
// This meta tag is for some weird reason needed for 100% google PWA ;-)
element = document.createElement("meta");
element.attr("name", "theme-color");
element.attr("content", getWebAppSettings().getThemeColor());
document.getElementsByTag("head").get(0).appendChild(element);

if (stronglyCachedResources == null) {
// TODO make this somehow more stable and cleaner
String text = document.body().toString().replaceAll("\n", "");

Pattern p = Pattern.compile(".*widgetset\": *\"([^\"]+)\"", Pattern.DOTALL);
Matcher matcher = p.matcher(text);
boolean find = matcher.find();
String wsname = matcher.group(1);

InputStream resourceAsStream = getClass().getResourceAsStream("/VAADIN/widgetsets/" + wsname + "/" + "safari.manifest");

// Might be null in case of for example fallback UI
if (resourceAsStream != null) {
List<String> resources = new ArrayList<>();

try {
final URI base = new URI(contextPath + "/VAADIN/widgetsets/" + wsname + "/");
// the safari permutation is used by most mobile web apps
// Read the cached files from the GWT generated manifest file for service workers as well
List<String> readLines = IOUtils.readLines(resourceAsStream);
boolean cachedFilesStarted = false;
for (String readLine : readLines) {
if (readLine.startsWith("CACHE:")) {
cachedFilesStarted = true;
resources.add(contextPath + "/");
continue;
}
if (readLine.startsWith("NETWORK:")) {
cachedFilesStarted = false;
continue;
}
if (cachedFilesStarted) {
readLine = readLine.trim();
if (!readLine.isEmpty()) {
URI resolved = base.resolve(readLine);
resources.add(resolved.toString());
}
}
}
} catch (IOException ex) {
Logger.getLogger(TouchKitSettings.class.getName()).log(Level.SEVERE, null, ex);
} catch (URISyntaxException ex) {
Logger.getLogger(TouchKitSettings.class.getName()).log(Level.SEVERE, null, ex);
}
this.stronglyCachedResources = resources;
}

}
if (stronglyCachedResources != null && !stronglyCachedResources.isEmpty()) {
Element serviceworkerregistration = document.createElement("script");
serviceworkerregistration.attr("type", "text/javascript");
serviceworkerregistration.appendText("if ('serviceWorker' in navigator) {\n"
+ " navigator.serviceWorker.register('"+contextPath+"/service-worker.js', { scope: '"+contextPath+"/' }).then(function(reg) {\n"
+ "\n"
+ " if(reg.installing) {\n"
+ " console.log('Service worker installing');\n"
+ " } else if(reg.waiting) {\n"
+ " console.log('Service worker installed');\n"
+ " } else if(reg.active) {\n"
+ " console.log('Service worker active');\n"
+ " }\n"
+ "\n"
+ " }).catch(function(error) {\n"
+ " console.log('Registration failed with ' + error);\n"
+ " });\n"
+ "}");
document.body().appendChild(serviceworkerregistration);
}

}
}

@Override
public void sessionInit(SessionInitEvent event) throws ServiceException {
event.getSession().addBootstrapListener(this);
event.getSession().addRequestHandler(new RequestHandler() {
@Override
public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException {
final String pathInfo = request.getPathInfo();
String contextPath = request.getContextPath();
if(pathInfo != null){
if (pathInfo.endsWith("manifest.json")) {
// TODO write manifest.json using Jackson or similar
PrintWriter writer = response.getWriter();
writer.append("{\n"
+ " \"short_name\": \"" + getWebAppSettings().getApplicationShortName() + "\",\n"
+ " \"name\": \"" + getWebAppSettings().getApplicationName() + "\",\n"
+ " \"display\": \"" + getWebAppSettings().getDisplay() + "\",\n"
+ " \"start_url\": \"" + contextPath + getWebAppSettings().getStartUrl() + "\",\n"
+ " \"background_color\": \"" + getWebAppSettings().getBackgroundColor() + "\",\n"
+ " \"theme_color\": \"" + getWebAppSettings().getThemeColor() + "\",\n"
+ " \"icons\": [\n");

final ApplicationIcon[] icons = getApplicationIcons().getApplicationIcons();
for (int i = 0; i < icons.length; i++) {
ApplicationIcon icon = icons[i];
if (i > 0) {
writer.println(",");
}
writer.print(" {\n");
writer.print("\"src\": \"" + icon.getHref() + "\",");
writer.print("\"sizes\": \"" + icon.getSizes() + "\"");
writer.print(" }\n");
}
writer.print(" ]\n");
writer.append("}");

return true;
} else if (pathInfo.endsWith("service-worker.js")) {
response.setContentType("text/javascript");
response.setCacheTime(-1);
PrintWriter writer = response.getWriter();
writer.write("self.addEventListener('install', e => {\n"
+ " e.waitUntil(\n"
+ " caches.open(\"tkcache\").then(c => {\n"
+ " return c.addAll([\n");

// TODO consider using https://github.com/GoogleChromeLabs/sw-precache
if (stronglyCachedResources != null) {
for (String stronglyCachedResource : stronglyCachedResources) {
writer.write("'");
writer.write(stronglyCachedResource);
writer.write("',\n");
}
} else {
Logger.getLogger(TouchKitSettings.class.getName()).log(Level.SEVERE, "strongly cached resources could not be found");
}

writer.write(""
+ " ]).then(() => self.skipWaiting());\n"
+ " })\n"
+ " );\n"
+ "});\n"
+ "\n"
+ "self.addEventListener('fetch', e => {\n"
+ " e.respondWith(\n"
+ " caches.open(\"tkcache\").then(c => {\n"
+ " return c.match(e.request).then(res => {\n"
+ " return res || fetch(e.request)\n"
+ " });\n"
+ " })\n"
+ " );\n"
+ "});");
return true;
}
}
return false;
}

}
);
}

/**
Expand All @@ -202,8 +387,8 @@ public ApplicationCacheSettings getApplicationCacheSettings() {
/**
* Sets the {@link ApplicationCacheSettings} instance to use.
*
* @param applicationCacheSettings
* the {@link ApplicationCacheSettings} instance to use.
* @param applicationCacheSettings the {@link ApplicationCacheSettings}
* instance to use.
*/
public void setApplicationCacheSettings(
ApplicationCacheSettings applicationCacheSettings) {
Expand All @@ -213,8 +398,7 @@ public void setApplicationCacheSettings(
/**
* Sets the {@link ApplicationIcons} instance to use.
*
* @param applicationIcons
* the {@link ApplicationIcons} instance to use.
* @param applicationIcons the {@link ApplicationIcons} instance to use.
*/
public void setApplicationIcons(ApplicationIcons applicationIcons) {
this.applicationIcons = applicationIcons;
Expand All @@ -223,8 +407,7 @@ public void setApplicationIcons(ApplicationIcons applicationIcons) {
/**
* Sets the {@link ViewPortSettings} instance to use by default.
*
* @param viewPortSettings
* the {@link ViewPortSettings} instance to use.
* @param viewPortSettings the {@link ViewPortSettings} instance to use.
* @see #addViewPortSettings(SettingSelector)
*/
public void setViewPortSettings(ViewPortSettings viewPortSettings) {
Expand All @@ -245,8 +428,7 @@ public void addViewPortSettings(
/**
* Sets the {@link WebAppSettings} instance to use.
*
* @param iosWebAppSettings
* the {@link WebAppSettings} instance to use.
* @param iosWebAppSettings the {@link WebAppSettings} instance to use.
*/
public void setWebAppSettings(WebAppSettings iosWebAppSettings) {
webAppSettings = iosWebAppSettings;
Expand All @@ -261,4 +443,22 @@ public SystemMessages getSystemMessages(
customizedSystemMessages.setSessionExpiredNotificationEnabled(false);
return customizedSystemMessages;
}

/**
* @return true if Google style service worker + json manifest style PWA
* should be used instead of original iOS style home screen web app thingies.
*/
public static boolean supportsGooglePWA() {
try {
VaadinRequest currentRequest = VaadinServletService.getCurrentRequest();
String useragentheader = currentRequest.getHeader("User-Agent").toLowerCase();
// Simply expect all chromes to support
if (useragentheader.contains("chrome")) {
return true;
}
} catch (Exception e) {
System.err.println("Detecting sw support failed!");
}
return false;
}
}
Loading