Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: render custom offline page in app shell #10360

Merged
merged 4 commits into from Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -395,8 +395,10 @@ export class Flow {
}

private async offlineStubAction() {
await import('./OfflineStub');
const offlineStub = document.createElement('vaadin-offline-stub') as HTMLRouterContainer;
const offlineStub = document.createElement('iframe') as HTMLRouterContainer;
const offlineStubPath = './offline-stub.html';
offlineStub.setAttribute('src', offlineStubPath);
offlineStub.setAttribute('style', 'width: 100%; height: 100%; border: 0');
this.response = undefined;

let onlineListener: ConnectionStateChangeListener | undefined;
Expand Down

This file was deleted.

8 changes: 4 additions & 4 deletions flow-client/src/test/frontend/FlowTests.ts
Expand Up @@ -19,8 +19,6 @@ const flowRoot = window.document.body as any;

const stubVaadinPushSrc = '/src/test/frontend/stubVaadinPush.js';

const OFFLINE_STUB_NAME = 'vaadin-offline-stub';

// A `changes` array that adds a div with 'Foo' text to body
const changesResponse = `[
{
Expand Down Expand Up @@ -655,7 +653,8 @@ suite("Flow", () => {
search: ''
};
const view = await route.action(params);
assert.equal(view.localName, OFFLINE_STUB_NAME);
assert.equal(view.localName, 'iframe');
assert.equal(view.getAttribute('src'), '/offline-stub.html');

// @ts-ignore
let onBeforeEnterReturns = view.onBeforeEnter(params, {});
Expand All @@ -682,7 +681,8 @@ suite("Flow", () => {

const view = await route.action(params);
assert.isNotNull(view);
assert.equal(view.localName, OFFLINE_STUB_NAME);
assert.equal(view.localName, 'iframe');
assert.equal(view.getAttribute('src'), '/offline-stub.html');

assert.equal(indicator.getAttribute('style'), 'display: none');

Expand Down
20 changes: 13 additions & 7 deletions flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java
Expand Up @@ -33,11 +33,14 @@
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Collectors;

import org.slf4j.LoggerFactory;

import com.vaadin.flow.server.communication.PwaHandler;
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;

import elemental.json.Json;
Expand Down Expand Up @@ -234,16 +237,19 @@ private String initializeRuntimeServiceWorker(
ServletContext servletContext) {
StringBuilder stringBuilder = new StringBuilder();

// List of icons for precache
List<String> filesToCache = getIcons().stream()
// List of files to precache
Collection<String> filesToCache = getIcons().stream()
.filter(PwaIcon::shouldBeCached).map(PwaIcon::getCacheFormat)
.collect(Collectors.toList());
.collect(Collectors.toCollection(LinkedHashSet::new));

// When offlinePath is in use, it is also an offline resource to
// When custom offlinePath is in use, it is also an offline resource to
// precache
if (pwaConfiguration.isOfflinePathEnabled()) {
filesToCache.add(offlinePageCache());
filesToCache.add(offlinePageCache(pwaConfiguration.getOfflinePath()));
}
// Offline stub to be shown within an <iframe> in the app shell
filesToCache
.add(offlinePageCache(PwaHandler.DEFAULT_OFFLINE_STUB_PATH));

// Add manifest to precache
filesToCache.add(manifestCache());
Expand Down Expand Up @@ -403,9 +409,9 @@ public String getRuntimeServiceWorkerJs() {
return runtimeServiceWorkerJs;
}

private String offlinePageCache() {
private String offlinePageCache(String offlinePath) {
return String.format(WORKBOX_CACHE_FORMAT,
pwaConfiguration.getOfflinePath(), offlineHash);
offlinePath, offlineHash);
}

private String manifestCache() {
Expand Down
Expand Up @@ -18,7 +18,9 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.flow.function.SerializableSupplier;
Expand All @@ -43,6 +45,7 @@
*/
public class PwaHandler implements RequestHandler {
public static final String SW_RUNTIME_PRECACHE_PATH = "/sw-runtime-resources-precache.js";
public static final String DEFAULT_OFFLINE_STUB_PATH = "offline-stub.html";

private final Map<String, RequestHandler> requestHandlerMap = new HashMap<>();
private final SerializableSupplier<PwaRegistry> pwaRegistryGetter;
Expand Down Expand Up @@ -79,17 +82,22 @@ private void init(PwaRegistry pwaRegistry) {
});
}

// Assume that offline page and offline stub (for display within app)
// are the same. This may change in the future.
List<String> offlinePaths = new ArrayList<>();
if (pwaRegistry.getPwaConfiguration().isOfflinePathEnabled()) {
// Offline page handling
requestHandlerMap.put(
pwaRegistry.getPwaConfiguration().relOfflinePath(),
(session, request, response) -> {
response.setContentType("text/html");
try (PrintWriter writer = response.getWriter()) {
writer.write(pwaRegistry.getOfflineHtml());
}
return true;
});
offlinePaths
.add(pwaRegistry.getPwaConfiguration().relOfflinePath());
}
offlinePaths.add("/" + DEFAULT_OFFLINE_STUB_PATH);
for (String offlinePath : offlinePaths) {
requestHandlerMap.put(offlinePath, (session, request, response) -> {
response.setContentType("text/html");
try (PrintWriter writer = response.getWriter()) {
writer.write(pwaRegistry.getOfflineHtml());
}
return true;
});
}

// manifest.webmanifest handling
Expand Down
Expand Up @@ -228,14 +228,11 @@ public void offlineTsView_navigateToServerView_offlineStubShown()
try {
$("main-view").first().$("a").id("menu-hello").click();

waitForElementPresent(By.tagName("vaadin-offline-stub"));
waitForElementPresent(By.tagName("iframe"));
WebElement offlineStub = findElement(
By.tagName("vaadin-offline-stub"));

Assert.assertFalse(
"vaadin-offline-stub shadow root expected to contain an element with class offline",
findInShadowRoot(offlineStub, By.className("offline"))
.isEmpty());
By.tagName("iframe"));
driver.switchTo().frame(offlineStub);
Assert.assertNotNull(findElement(By.className("offline")));
} finally {
setConnectionType(NetworkConnection.ConnectionType.ALL);
}
Expand All @@ -252,14 +249,11 @@ public void offlineServerView_navigateToServerView_offlineStubShown()
try {
$("main-view").first().$("a").id("menu-hello").click();

waitForElementPresent(By.tagName("vaadin-offline-stub"));
waitForElementPresent(By.tagName("iframe"));
WebElement offlineStub = findElement(
By.tagName("vaadin-offline-stub"));

Assert.assertFalse(
"vaadin-offline-stub shadow root expected to contain an element with class offline",
findInShadowRoot(offlineStub, By.className("offline"))
.isEmpty());
By.tagName("iframe"));
driver.switchTo().frame(offlineStub);
Assert.assertNotNull(findElement(By.className("offline")));
} finally {
setConnectionType(NetworkConnection.ConnectionType.ALL);
}
Expand All @@ -274,11 +268,11 @@ public void offlineStub_backOnline_stubRemoved_serverViewShown()
setConnectionType(NetworkConnection.ConnectionType.AIRPLANE_MODE);
try {
$("main-view").first().$("a").id("menu-hello").click();
waitForElementPresent(By.tagName("vaadin-offline-stub"));
waitForElementPresent(By.tagName("iframe"));

setConnectionType(NetworkConnection.ConnectionType.ALL);

waitForElementNotPresent(By.tagName("vaadin-offline-stub"));
waitForElementNotPresent(By.tagName("iframe"));
Assert.assertNotNull(findElement(By.id(NAVIGATE_ABOUT)));
} finally {
setConnectionType(NetworkConnection.ConnectionType.ALL);
Expand Down