Switching application root (setRoot()) breaks after the second run
- Dominant language
- TypeScript
- Stars
- 11.7k
- Forks
- 608
- PR merge metrics
- No merged PRs in 30d
Description
Firstly, apologies for the long-winded explanation but I thought it makes sense to explain everything step by step to make it easier to replicate the issue.
#### Background
I am writing a website that requires users to log in to access protected content. As expected, the login page has a completely different layout to all the authenticated pages that contain elements like toolbar, sidebar, navigation, etc.
Due to the lack of documentation I've not been able to find a definitive guide on what's the preferred way to implement multiple layouts and have, as a result, settled on having to have different application roots that I switch depending on whether the user is authenticated or not (for the purpose of this example I'll call them `Login` and `App`).
#### Application flow
When the application is first launched the `configure(aurelia: Aurelia)` method in `main.ts` checks if the current user is authenticated and if not sets the root to `Login` in the result of which the user is presented with the login form.
``` typescript
export function configure(aurelia: Aurelia) {
aurelia.start()
.then(() => {
let root = authService.hasIdentity() ? 'app' : 'login';
aurelia.setRoot(root);
});
}
```
When the user submits the form and successfully authenticates with the server, the application stores the authentication identity locally, sets the root to `App` and redirects user to the welcome page:
``` typescript
this.authService
.login()
.then(() => {
this.aurelia.setRoot('app')
.then(() => {
this.router.navigate('/welcome');
});
});
```
After that the user can log out of the session, which clears the local identity, sets the root back to `Login` and redirects user to `/`:
``` typescript
this.authService
.logout()
.then(() => {
this.aurelia.setRoot('login')
.then(() => {
this.router.navigate('/');
});
});
```
At this point, if the user wants to log back in, the expected behaviour is that they click on the Login button, the root gets changed to `App` and they get redirected to `/welcome`, yet that is not the case as the promise returned by `setRoot()` never resolves and as a result the user remains on the same page.
The user does, however, get redirected if he/she clicks on the Login button again right after the previous attempt and from that point on the login -> logout -> login -> logout -> ... flow works as expected - the roots change and users don't get "stuck" on the login page.
#### Test case repository
I've created a simple test application (based on the Typescript skeleton app) which you can find here:
https://github.com/codeaid/skeleton-navigation
To make it easier to understand what I've done there, the application consists of the following core components:
- _Authentication service_ - validates credentials with the server and allows storing and clearing authentication identities locally on the clients. The "validation" is actually only a sleep to simulate a request delay.
- _Authorization step_ - ensures that a local identity is present before navigating to pages that have `{ settings: { auth: true } }` configured on their routes. If no identity is present, it changes the application root to `login` and redirects to the login page.
- _Router configuration service_ - invoked only once in the `main.ts` file to register all available routes and add the authorization step.
- _Login_ - The login root/page.
- _Page1 and Page2_ - Two pages that require authentication identity to be present to access them.
The installation is the same as always:
``` shell
cd ./skeleton-typescript
npm install
jspm install
gulp watch
```
#### Using the test application
To replicate the issue I've described above simply `gulp watch` the project, switch to your browser and navigate to `/`. You will be presented with the login page containing a _Login_ button and two buttons allowing you to navigate to `/page1` and `page2`.
Click the _Login_ button after which your local identity will get set and you will be redirected to page1. After that happens, click on the _Log out_ button and you will be redirected back to the login page.
At this point if you click on the _Login_ button again nothing happens and you remain on the same page. If you click on the button again, though, you get redirected to `/page1` and if you keep logging out and in it works as expected.
#### Potential culprit
I tried to debug the code to understand what is causing the issue but ended up in the `CompositionEngine` and eventually gave up as I'm not even sure what's happening in there.
I placed a tonne of `console.info` calls around the `aurelia-templating.js` and got the impression that for some reason the promise returned from `waitForCompositionComplete()` does not get resolved and as a result the `swap()` function doesn't get triggered.
Here's the code I'm talking about, which could be the culprit (trimmed to only show the important bits):
``` js
CompositionEngine.prototype._createControllerAndSwap = function _createControllerAndSwap(context) {
function swap(controller) {
// ...
}
return this.createController(context).then(function (controller) {
if (context.compositionTransactionOwnershipToken) {
return context.compositionTransactionOwnershipToken.waitForCompositionComplete().then(function () {
// this does not seem to get resolved
return swap(controller);
});
}
// context.compositionTransactionOwnershipToken is not empty, hence this doesn't seem to fire
return swap(controller);
});
};
```
That's as much as I can tell, the rest is in your hands! :)
Hope that helps and yet again, sorry for the wall of text!
Contributor guide
Research direction
Reproduce the repeated login/logout flow in the linked skeleton-navigation test application using the installation steps in the issue. Start with configure in main.ts and the CompositionEngine._createControllerAndSwap path in aurelia-templating.js, then trace waitForCompositionComplete during repeated setRoot calls. Done means the setRoot promise resolves and redirects work on the first login after logout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100