When I try to navigate a user asynchronously from the server-side via #Push like:
ListenableFuture<JobApplicationMatrix> listenableFuture = // some Async method invocation
var ui = UI.getCurrent();
listenableFuture.addCallback(jobApplicationMatrix -> {
ui.access(() -> {
ui.navigate(CandidateComplianceApplicationView.class, new RouteParameters(CandidateComplianceApplicationView.APPLICATION_UUID_PARAMETER, candidateApplicationUuid));
});
}
it is not working as expected. CandidateComplianceApplicationView is a secured View. When I execute ui.navigate from ui.access the system moves me to the Keycloak login page. Looks like the system doesn't understand that the user is logged in. So, is this possible to navigate to the secure view from ui.access and if so, what am I doing wrong here?
I also tried to change #PermitAll to #AnonymousAllowed on CandidateComplianceApplicationView. In such case the system moves me to the correct page but the actual content of the CandidateComplianceApplicationView is not rendered at all.
UPDATED
I use
#Push(transport = Transport.LONG_POLLING)
Related
So, I wrote a Nuget that allows dev in my company to quickly setup authorization using OIDC. Part of that, of course, is handing off the authentication step to a browser so I'm simply invoking Browse.OpenAsync with LaunchMode = BrowserLaunchMode.SystemPreferred. I also set up a HTTP listener that awaits the ahtority's authorization code callback. All works find but the browser window is of course still sitting at the top, hiding the initiating app.
I do realise I can use deep linking to simply "call up" the app again but that has two implications I would like to avoid: The need to register custom URI schema with the mobile OS and, above all, the need to register that (custom) callback URI with my company's API management system (which doesn't like anything but "http" or "https" URI schemes). Those requirements makes it more complicated for our devs to get started and, like I mentioned, it doesn't work very well with our API management system, so I'm looking for a simpler solution.
For iOS I can simply fetch the key window's view controller and dismiss it, like so:
async Task<Outcome> IPlatformService.TryCloseTopWindowAsync(bool isModalWindow, bool animated)
{
var window = UIApplication.SharedApplication.KeyWindow;
var viewController = window?.RootViewController;
if (viewController is null)
return Outcome.Fail("Couldn't obtain top window view controller");
try
{
if (isModalWindow)
{
viewController.DismissModalViewController(animated);
return Outcome.Success();
}
await viewController.DismissViewControllerAsync(animated);
return Outcome.Success();
}
catch (Exception ex)
{
return Outcome.Fail(ex);
}
}
Unfortunately, I'm not very good with Android's (Java based) APIs so I'm wondering if it's possible to do something similar here? Can I get the top app (activated by my own app) and dismiss it? If so; how?
I followed the Vaadin tutorial (Creating Collaborative Views) for broadcasting events and register on them.
Registration eventRegistration;
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
#Override
protected void onDetach(DetachEvent detachEvent) {
log.debug("In detach...");
if(eventRegistration != null) {
eventRegistration.remove();
eventRegistration = null;
}
}
Everything works except the fact that when refreshing the page, the logic in the onDetach() is not executed. After refresh, however, you will enter the onAttach() method. Because of this you are actually going to register several of 'the same' listeners without removing the previous one and you actually get a doubling of listeners. The onDetach() method is only accessed if you go to another menu item, for example.
You can find an example log below.
What is the Vaadin recommended way to remove these listeners before/during refresh?
The onDetach method should be called eventually.
No event is sent to the server when you close or refresh a tab, and as such the server is not aware that the old UI should be detached.
This is where the heartbeat requests come in. The UIs send heartbeat requests every 5 minutes per default, and if the server notices that the old UI has missed three heartbeats, it will be detached. Alternatively, it will be detached when the session expires.
In other words, the onDetach method should be called after about 20 minutes.
The reason no event is sent to the server when the tab is closed or refreshed is that this could prevent the tab from refreshing/closing while the request is being handled, which is bad user experience. Also, this wouldn't cover the cases where the computer is turned off or the network disconnected.
There is something called the Beacon API that could be used to notify the server when a tab is refreshed or closed without causing a delay in the browser. There is an issue for using this to immediately detach UIs.
I'd recommend using the Unload Beacon add-on: https://vaadin.com/directory/component/unload-beacon-for-vaadin-flow or a similar approach which is demonstrated in the Cookbook: https://cookbook.vaadin.com/notice-closed - essentially, it's executing the JavaScript snippet to add an event listener for Window's unload event:
ui.getElement().executeJs(
"window.addEventListener('unload', function() {navigator.sendBeacon && navigator.sendBeacon($0)})", relativeBeaconPath);
and the beacon is sent to a custom SynchronizedRequestHandler.
Simplest way to workaround the problem would be checking if eventRegistration is null before adding one.
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
if (eventRegistration == null) {
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
}
Check the other answer by Erik why calling of onDetach is delayed.
in my Vaadin 14.2.0 application there is a BeforeLeaveListener to show a confirmation dialog when the input is dirty (= there are unsaved changes in the input fields) to cancel (or intentionally proceed) the navigation:
BeforeLeaveListener listener = new BeforeLeaveListener() {
#Override
public void beforeLeave(BeforeLeaveEvent event) {
if (dirtyFlag.isDirty()) {
ContinueNavigationAction postponeAction = event.postpone();
// show confirmation dialog and maybe execute a proceed
[...] () -> postponeAction.proceed();
}
}
};
UI.getCurrent().addBeforeLeaveListener(listener);
This works fine for everything but for the logout.
This is how my logout button looked like in the start (what works as long as I do not want to postpone/cancel the logout):
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
});
Now I want to postpone the logout until the user confirms that the unsaved changes should get discarded. (Switching both lines in the EventListener seems to be a good idea, but did not work because of the following.)
Within the navigate-call the BeforeLeaveListener is called (good), but the logout is done nevertheless, because the code after the navigate()-call is executed (of course, because it is no blocking call), the session is invalidated and the user is logged out though the confirmation dialog just popped out (but the user had no chance to confirm/deny).
I tried to move the session invalidation into the LoginView (into a BeforeEnterObserver), but the result is that the login view is reloaded in an endless loop.
Question: is there something like "navigate to LoginView and if navigation is not postponed and not cancelled, then invalidate session"?
A workaround is to navigate to an intercepting LogoutView that just invalidates the session and redirects/forwards to the LoginView:
#Route(value = "logout")
public class LogoutView implements BeforeEnterObserver {
#Override
public void beforeEnter(BeforeEnterEvent event) {
VaadinSession.getCurrent().getSession().invalidate();
event.forwardTo(LoginView.class);
}
}
But this seems to me to be just a bad workaround with some overhead (creating a view just to forward to another view)...
I know, you didnt mention Spring. But that's actually what I believe Spring Security is doing under the hood.
My Logout Button (using Spring Security) is looking like this:
new Button("Logout", click -> {
UI.getCurrent().getPage().executeJs("location.assign('logout')");
});
Once you click it, you are logged out and redirected to the login view (more details from Baeldung). I think it will be just fine if you do your LogoutView redirection detour.
The fact that you can even use the Navigator to go to your LogoutView is even better here, since this is caught by BeforeLeaveObserver (in contrast to completely new requests by assigning a new location or refreshing the page. See BeforeLeaveObserver vs. beforeunload for more details on that)
I would still like to propose another idea for your usecase. I think it will be much simpler, but requires that the logout link knows of the dirtyFlag:
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
if(dirtyFlag.isDirty()){
// show Dialog with
// - a confirm btn that calls doLogout() on click
// - a cancel btn that closes the Dialog
} else {
doLogout();
}
});
...
private void doLogout(){
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
}
I have a JSF Phase Listerner that checks to see if the user is logged in, and if not, redirects them to the login page. This is working fine for non-ajax requests. However, if the user is on a page, in my case, one that has a primefaces data table, and clicks on a button that invokes an ajax request -- but their session has timed out -- the code gets executed that issues the redirect (using ExternalContext#redirect), however the user is not navigated to the login page.
Any idea why this is not working?
Here is my phase listener:
private static final String IS_LOGGED_IN_INDICATOR = "loggedIn";
private static final String LOGIN_PAGE = "/login.jsp";
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession)ec.getSession(false);
if (session==null || session.getAttribute(IS_LOGGED_IN_INDICATOR) == null) {
try {
ec.redirect(LOGIN_PAGE);
}
catch(IOException e) {
// log exception...
}
}
}
public void afterPhase(PhaseEvent event) {
// no-op
}
}
It failed because the ajax context is trying to obtain the render kit from the view root, while there is no view root at all. It has not been restored at that point yet. This resulted in a NullPointerException in PartialViewContext#createPartialResponseWriter(). This exception is in turn not been thrown, but instead been put in an ajax exception queue which is supposed to be handled by a custom ExceptionHandler. You apparently don't have any one. This exception is visible if you create/use such one like the FullAjaxExceptionHandler (see also this blog for more detail).
To fix the particular problem, do the job in afterPhase() instead. The view root is then fully restored and the ajax context can obtain the render kit from it in order to write a specialized XML response which instructs the JSF ajax engine in JavaScript to change the window location. Without ajax, a render kit was not necessary as a redirect is basically just a matter of setting a response header.
Whether the particular NullPointerException is in turn a bug in Mojarra or not is a different question which can better be posted in flavor of an issue report at their own issue tracker.
this is because you have to send a special response in XML for Ajax request in order to do redirect (check this answer) , I have implemented this in a Filter like this..
// Check if it's an Ajax Request
if ("partial/ajax".equals(((HttpServletRequest) request).getHeader("Faces-Request"))) {
//redirect
response.setContentType("text/xml");
response.getWriter()
.append("<?xml version= \"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>",url);
you should port this to your Phase Listener.
In a ASP.Net MVC 2 application I want to do the following: in the action that handles a form post I want to:
Redirect the user to other view in the current browser window
Open a new window showing other info (other view)
That can be done easily setting the target="_blank" attribute in the form element and adding the following jQuery script:
$(function () {
$("form").submit(function () {
window.location = "...";
});
});
The View returned by the action handler will be rendered in the new window where the form is posted to.
But, let's make it a little trickier:
If there are no service layer errors when executing the action, then do the above.
If there's any service layer error when executing the action, then do not open the new window and the view returned by the action must be shown in the same window where the form was in the first place.
E.g.: Imagine that the service layer generates a pdfDocument to show to the user if everything is ok, and that pdf must be shown in a new window.
[HttpPost]
public ActionResult SomeAction(FormCollection form)
{
var serviceMethodParams = ... // convertion from the form data somehow
MemoryStream pdfDocument = null;
if (!serviceLayer.DoSomething(serviceMethodParams, out pdfDocument))
{
// Something went wrong, do not redirect, do not open new window
// Return the same view where error should be displayed
return View(...);
}
// The service method run ok, this must be shown in a new window and the origal window must be redirected somewhere else
return File(pdfDocument.ToArray(), "application/pdf");
}
Note that the original solution work fine when the service returns true, but if the service returns false the view showing the errors is shown in a new window and the original window is redirected somewhere else.
In this situation, the better option would be to simply return a URL specific to that document for the user to point to, and your ajax call, when successful, takes the URL returned from your action method, and then opens a new window itself pointing to that URL. On error, you can display errors or some such - basically, that data is folded in a Json return value.
Outside of an ajax call, the most acceptable pattern to me is to have the view re render, then attach some startup javascript to open the new window pointing to that specific URL.