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
Flow initial pwa #4334
Flow initial pwa #4334
Conversation
Initial commit: - Unstable - Quick'n'dirty - Works
Fixed code and tests to pass tests. Removed initializer - registry created as singleton on demand. Added support for free size icons.
@@ -1,4 +1,4 @@ | |||
com.vaadin.flow.server.startup.ServletVerifier | |||
com.vaadin.flow.server.startup.RouteRegistryInitializer | |||
com.vaadin.flow.server.startup.ErrorNavigationTargetInitializer | |||
com.vaadin.flow.server.startup.AnnotationValidator | |||
com.vaadin.flow.server.startup.AnnotationValidator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert file. Now the only change is that we don't have a new line in the file.
* @param height height of icon | ||
* @return | ||
*/ | ||
public Icon size(int width, int height) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setSize instead of size
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye, there are chaining setters used in Icon with just property name for each did on purpose. Using setFoo with chaining seems not so good. I'll change those of course if needed. For instance to withFoo if it would be better? Or remove chaining totally, if it doesn't suite.
return element; | ||
} | ||
|
||
private Icon attr(String key, String value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For readability of code. addAttribute
* | ||
* @return a String as [size]x[size] | ||
*/ | ||
public String sizes() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getSize
* @param href href | ||
* @return self | ||
*/ | ||
public Icon href(String href) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setHref
* @return | ||
*/ | ||
public List<String> getOfflineResources() { | ||
return offlineResources.stream().collect(Collectors.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collections.unmodifiableList(offlineResources)
@@ -292,7 +295,10 @@ public void init() throws ServiceException { | |||
handlers.add(new UidlRequestHandler()); | |||
handlers.add(new UnsupportedBrowserHandler()); | |||
handlers.add(new StreamRequestHandler()); | |||
|
|||
PWARegistry pwaRegistry = getPwaRegistry(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default implementation in VaadinServletService may return null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null checking added
this.pwaRegistry = PWARegistry | ||
.initRegistry(getServlet().getServletContext()); | ||
} catch (IOException e) { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning null throw a new NamedException extends RuntimeException
with information on what failed and why as now things will fail with a NPE at some point.
requestHandlerMap = new HashMap<>(); | ||
this.pwaRegistry = pwaRegistry; | ||
|
||
if (this.pwaRegistry.getPwaConfiguration().isEnabled()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if and contents could be in a init()
with a direct return if not enabled for clearer reading.
@@ -1018,18 +1018,20 @@ public void theme_contents_are_appended_to_head() | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test changes do not add test for pwa.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, tests are missing. Need to add those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests added
Added missing periods Use defined constant Fixed typo (extra ") from meta tag value Changed to use unmodifiableList
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the fluent API I guess it's fine, but at least the getters should be in the getXYZ format.
* | ||
* Only 1 annotation for application is supported. | ||
* | ||
* Annotation must be placed to master layout. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's another problem also. Now if you for some reason (Forgetting a PWA when copying or something) have multiple PWA annotations then one of them will be selected and you can't really know which one, might even be a different one each time if you are unlucky.
* Defines the developers’ preferred display mode for the website. | ||
* | ||
* Possible values: | ||
* fullscreen, standalone, minimal-ui, browser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered making this an enum?
* | ||
* @return Is PWA injecting enabled | ||
*/ | ||
boolean enabled() default true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a use case when someone adds the @PWA
annotation but with enabled=false
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No there's not. That was leftover from old code. Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to publish my review in time. Sorry, new UI of GitHub review.
private long fileHash = 0; | ||
private String baseName = "icons/icon.png"; | ||
private Domain domain = Domain.HEADER; | ||
private boolean hrefOverride = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need hrefOverride
? It's not set anywhere.
tag
is also used in one place only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also basename
and domain
initial values are not used ever: those are always overwritten in constructor.
Looks suspicious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftovers from old code, removed
* @param outputStream output stream | ||
* @throws IOException possible exception in stream writing | ||
*/ | ||
public synchronized void write(OutputStream outputStream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to make this synchronized
?
* Exception thrown when {@link PWARegistry} initialization fails. | ||
* | ||
*/ | ||
public class PWAInitializationException extends RuntimeException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it's used in one place only and it's our code.
Does it make any sense to have this exception? Can we just throw UncheckedIOException
there instead?
this.offlinePath = checkPath(pwa.offlinePath()); | ||
this.display = pwa.display(); | ||
this.startUrl = getStartUrl(servletContext); | ||
this.serviceWorkerPath = "sw.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the same in both cases, is it ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover from when it was supposed to be configured, but it's a bit dangerous to let it be configured, so it was removed. I'll change it away from different blocks, for that's fixed value.
if (pwa != null) { | ||
this.appName = pwa.name(); | ||
this.shortName = pwa.shortName().substring(0, | ||
Math.min(pwa.shortName().length(), 12)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we warn the user that he did not manage to fit into 12 symbols?
* | ||
* @return Path to logo with prefix, so request matches | ||
*/ | ||
public String relLogoPath() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't really understand why do we need two similar methods.
Can we leave only one and inline this instead, since it's used in a single place only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same about other rel
prefixed methods here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rel -prefix was added there to move logic of getting name in object, in stead of adding that logic (very simple, though) to place where needed. I liked the idea of leaving logic to the object. Sure it can be removed, if it doesn't feel right.
} | ||
|
||
// Google Workbox import | ||
stringBuilder.append("importScripts('https://storage.googleapis.com/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean we depend on some external CDN resource now?
Is it safe? Afaik we did not do such thing before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workbox files (needed ones) are now served internally via flow
@@ -701,6 +702,42 @@ private static void setupMetaAndTitle(Element head, | |||
}); | |||
} | |||
|
|||
private static void setupPWA(Element head) { | |||
PWARegistry registry = VaadinService.getCurrent().getPwaRegistry(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VaadinService.getCurrent()
Please don't do this.
Artur made an effort to avoid getCurrent()
methods call whenever it's possible.
Please pass BootstrapContext context
as a method parameter and use it to get VaadinSession
and a service from it. There are examples in the methods of this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface PWA { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of methods (attributes) in this annotation.
So in the worst case the usage of this annotation will look like
@PWA(offlinePath="abc", manifestPath="foo", logoPath="bar", name="sdfsdf", shortName="cccc",...........................)
Looks inconvenient to me.
I'm not sure exactly how it may be improved.
But may be use PWA
as an enabler and delegate all the properties to the class like it's done for @Theme
annotation?
I would change in this case PWA
to EnablePWA
and add class or interface which contains all the methods from here (let's say its name is PWAConfig
).
Then EnablePWA
would be:
public @interface EnablePWA {
Class<? extends PWAConfig> value();
}
Then there will be an additional responsibility to instantiate the provided class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye, there are quite a lot of those properties. But I like the developer experience of having all that in that annotation: that way adding values and/or checking default values is supported directly with IDE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
private Map<String, String> attributes = new HashMap<>(); | ||
private String tag = "link"; | ||
|
||
protected PWAIcon(int width, int height, String baseName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think SQ requires Javadocs for every protected method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way why there is no any public
CTOR?
Is it intended to be used internally only (then may be package local?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of them are used internally only, have changed to package local.
Just to note, there were arguments about using package local in our code that ended up with using protected
instead. But I like package local more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
* @param outputStream output stream | ||
* @throws IOException possible exception in stream writing | ||
*/ | ||
public synchronized void write(OutputStream outputStream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any specific reason why this method is synchronized
?
I don't see any reason from the code.
The instance of PWAIcon
is not intended to be shared between threads (otherwise it requires special design for that which it doesn't have at the moment).
This is the only synchronized method and synchronization is done against the PWAIcon
instance.
If external client of this class decides to use it in concurrent code then it's the client responsibility to make synchronization. And this synchronization will be done with totally different monitor (object to lock via synchronization) than PWAIcon
instance.
So please remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason whatsovere. Was left over from some initial steps of creating PWA. Good spot. Removed.
private final boolean enabled; | ||
private List<String> offlineResources; | ||
|
||
protected PwaConfiguration(PWA pwa, ServletContext servletContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here you copy values from annotation to the PwaConfiguration
instance class.
So I would say this class should be used directly as I suggested in PWA
.
It also will allow to avoid big if-else
since yo may provide default method implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, initial version of this used that, but @interface is somewhat weak on what it can do (clearing up data and have logic and such). But yes, everything from PwaConfiguration
could be moved directly to PwaRegistry
and use just PWA
internally. Configuration was done with to get data of PWA
annotation nicely in an object. I liked the separation, but sure, it's not needed. But I still prefer having it separately instead of pouring everything to PwaRegirsty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I meant the other way around......
You are using PWA
annotation and it has a huge number of methods. Instead you may require only value
which will be PWAConfig
subclass and this subclass should be implemented by the developer and set as a value
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what to do here. I prefer an annotation with parametetrs approach over the class one and it seems like you've also agreed with it a few notes up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just explained the original idea here to avoid misunderstanding.
I think that a lot of parameters in the annotation makes it inconvenient to use but that's an opinion which needs a DX.
Do not block this.
(session, request, response) -> { | ||
response.setContentType("text/html"); | ||
response.getWriter().write(pwaRegistry.getOfflineHtml()); | ||
response.getWriter().close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here (actually I think there are places above where close
is also not inside finally
) and about lambda.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
} | ||
OutputStream out = response.getOutputStream(); | ||
icon.write(out); | ||
out.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure does icon.write
declares any throws
but event if it doesn't there is always a chance to get RuntimeException
.
All streams should be closed anyways. So close
should always be in finally
block.
And may be extract the lambda as a method reference because it's hard to read lambda inside for
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
response.setContentType("application/json"); | ||
PrintWriter writer = response.getWriter(); | ||
writer.write(this.pwaRegistry.getManifestJson()); | ||
writer.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
response.setContentType("application/javascript"); | ||
PrintWriter writer = response.getWriter(); | ||
writer.write(this.pwaRegistry.getServiceWorkerJs()); | ||
writer.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
|
||
protected void setPwaClass(Class<?> pwaClass) { | ||
if (pwaClass != null && pwaClass.isAnnotationPresent(PWA.class)) { | ||
this.pwaConfigurationClass = pwaClass; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That needs to be atomic since the method is called from the other thread than getter
.
Use AtomicReference
for the pwaConfigurationClass
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done by Mikko.
@denis-anisimov I've looked through your last comments and fixed everything that was missing here. |
b96f49f
to
689730a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of places where StringBuilder
is used but with string concatenation.
Everything else is fine.
String workBoxAbsolutePath = servletContext.getContextPath() + "/" | ||
+ WORKBOX_FOLDER; | ||
// Google Workbox import | ||
stringBuilder.append("importScripts('" + workBoxAbsolutePath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string concatenation within string builder append method looks weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with append
calls.
SonarQube analysis reported 7 issues Note: The following issues were found on lines that were not modified in the pull request. Because these issues can't be reported as line comments, they are summarized here:
|
Initial version of PWA.
Sets up simple PWA stack (manifest, service worker, icons, offline handling) for flow application. Configurable via PWA -annotation.