ui.router netsted state which defined in another module not work when refresh page - express

My app has two angular modules (hub and bApp). hub module is a container for another modules (bApp). I use ui.router for routing and config as follow as:
In hub.js (hub module) file:
var hub = angular.module('hub', ['ui.router', 'bApp']).config(function ($stateProvider) {
$stateProvider.state({
name: 'hub',
url: '/',
templateUrl: 'hub/views/main.html',
controller: 'MainCtrl'
}).state({
name: 'hub.frontends',
url: 'frontend',
templateUrl: 'hub/views/frontend.html',
controller: 'FrontendCtrl',
resolve: {
frontendList: function (getList) {
return getList.getAll('/api/frontend');
}
}
})
$locationProvider.html5Mode({ enabled: true, requireBase: true }).hashPrefix('!');
});
In bApp.js (bApp module) file:
var bApp = angular.module('bApp',[]).config(function($stateProvider){
$stateProvider.state({
name: 'hub.book',
url: 'book',
templateUrl: 'book/views/book.html',
controller: 'bookCtrl',
resolve: {
bookList: function(getList){
return getList.getAll('/api/book');
}
}
}).state({
name:'hub.book.test',
ulr:'book/test',
template: '<h1>test</h1'
});
});
Finally in express server, I config as follow as (just for working with html5Mode):
app.all('/user/*', function (req, res, next) {
res.sendFile('static/user/index.html', { root: __dirname });
});
Everything works well if I only navigate between states by $state.go. But when I refresh or enter url to browser and press enter, only states which defined in hub module is work.
States which defined in bApp module is not work. But hub.run() still work normally, but just show a blank page with no error.
I don't know how to figure out this problem.
Thanks!

Related

Page refresh or direct load shows blank screen

I've read a number of solutions to this same problem, but none have worked for me. So here it goes.
I have a Vue 2 app using Express that runs on AWS Amplify. When I run my app locally in 'dev' mode (npm run serve) and 'start' mode (npm run build && node server.js), everything works fine.
The problem shows up when I deploy to Amplify. I can click nav buttons, go back, and go forward, all of which send me to the correct URL. However, the moment I refresh the page or manually enter a valid URL in the browser, the screen goes blank and the browser URL shows https://dontblowup.co/index.html.
Below is my server.js file:
const express = require('express')
const path = require('path')
const history = require('connect-history-api-fallback')
const app = express()
const port = process.env.PORT || 8080
const buildLocation = 'dist'
const staticFileMiddleware = express.static(path.resolve(__dirname, buildLocation))
app.use(staticFileMiddleware)
app.use(history())
app.use(staticFileMiddleware)
app.get('*', function (req, res) {
res.sendFile(path.resolve(__dirname, buildLocation, 'index.html'))
})
app.listen(port, () => {
console.log(`App listening to port ${port}...`)
console.log('Press Ctrl+C to quit.')
})
The solutions I found included using the history package in the server.js file and using the staticFileMiddleware before and after using history(). I also have 'history' mode set in the Vue app's router.js file (see below).
import Vue from "vue";
import VueRouter from "vue-router";
import Index from "./views/Index.vue";
import MainFooter from "./layout/MainFooter.vue";
import DashboardLayout from "#/layout/DashboardLayout.vue";
import Analytics from "#/views/Analytics.vue";
import TradeSheet from "#/views/TradeSheet.vue";
import KellyCriterionCalculator from "#/views/KellyCriterionCalculator.vue";
import PositionBuilder from "#/views/PositionBuilder.vue";
Vue.use(VueRouter);
export default new VueRouter({
mode: 'history',
routes: [
{
path: "/",
name: "Index",
components: { default: Index, footer: MainFooter },
props: {
footer: { backgroundColor: "black" }
},
meta: { requiresAuth: false }
},
{
path: "/dashboard",
redirect: "/dashboard/analytics",
name: "Dashboard",
component: DashboardLayout,
meta: { requiresAuth: true },
children: [
{
path: "/dashboard/analytics",
name: "Analytics",
component: Analytics
},
{
path: "/dashboard/trade-sheet",
name: "Trade Sheet",
component: TradeSheet
},
{
path: "/dashboard/risk-budget-calculator",
name: "Risk Budget Calculator",
component: KellyCriterionCalculator
},
{
path: "/dashboard/trade-analyzer",
name: "Trade Analyzer",
component: PositionBuilder
}
]
}
],
scrollBehavior: to => {
if (to.hash) {
return { selector: to.hash };
} else {
return { x: 0, y: 0 };
}
}
});
At this point I'm convinced there's something wrong with Amplify or Namecheap (where my DNS is configured). Unfortunately I haven't found anyone with the same issues using the same tech, so hopefully someone here can help.
Cheers!
You need to set up it on the Amplify Console
Navigate to the Amplify Console
On the left menu click on "Rewrites and redirects"
Click on Edit
Add the rule:
Source: </^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>
Target: /
Type: 200
You can read more about it here
Go to section: Redirects for Single Page Web Apps (SPA)
Most SPA frameworks support HTML5 history.pushState() to change browser location without triggering a server request. This works for users who begin their journey from the root (or /index.html), but fails for users who navigate directly to any other page. Using regular expressions, the following example sets up a 200 rewrite for all files to index.html except for the specific file extensions specified in the regular expression.
the better way to handle SPA call to index.html will be
app.get(/.*/, (req, res) => res.sendFile(__dirname + '/dist/index.html'))
in frontend vue router you need to add a redirect route like this
{
path: "*",
redirect: "/"
}

Params field is empty in $router.push

Consider this:
this.$root.$router.push({
path: '/dashboard',
params: { errors: 'error' },
query: { test: 'test' }
})
I use this in my component to redirect to another URL, and some error has occured. The problem is that when I want to access params field in dashboard component, it's empty. The query field works well. I'm trying to access it by this.$route.params.errors.
You can use params only with named paths (i think).
Example:
//route (in your router file should have "name")
{ path: '/errors', name: 'EXAMPLE', component: ... }
//navigating
this.$router.push({
name: 'EXAMPLE',
params: { errors: '123' }
});
Now it will have correct value in this.$route.params.
If you don't want to use named routes you can try this:
ES6
this.$root.$router.push({
path: `/dashboard/${error}`,
query: { test }
})
ES5
this.$root.$router.push({
path: '/dashboard/' + error,
query: { test: 'test' }
})
I faced the similar issue where in one of my views (component). I was trying to navigate (programmatically) from /foo/bar to /foo/bar/123, but the route param was not available later in the component. My relevant navigation code looked like below:
methods: {
save_obj() {
let vm = this;
// Make AJAX call to save vm.my_obj, and on success do:
let v = `${vm.$route.path}/${vm.my_obj.id}`;
console.log("Loading view at path: "+v);
vm.$router.push({ path: v });
},
...
}
It would print the expected log (e.g., Loading view at path: /foo/bar/112), however, the loading of data in the created() hook would not receive the value of route param. My failing created() code looked like below:
created: function() {
console.log("Loading object details.");
let vm = this;
let cid = vm.$route.params.id; // <---- This was the problem
vm.$http.get('api/'+cid)
.then(function (res) {
if (res.data.status == "OK") {
vm.my_obj = res.data.body;
} else {
vm.setStatusMessage(res.data.body);
}
})
.catch(function (error) {
console.log(error);
vm.setStatusMessage("Error: "+error);
});
}
The solution was indicated in the third note here quoted below :
Note: If the destination is the same as the current route and only
params are changing (e.g. going from one profile to another /users/1
-> /users/2), you will have to use beforeRouteUpdate to react to changes (e.g. fetching the user information).
I had to do the following in my component:
Change the line let cid = vm.$route.params.id; in created() to let cid = vm.course.id
and, add the following to the component:
beforeRouteUpdate(to, from, next) {
if (to.params.id) {
this.my_obj.id = to.params.id;
}
// Some other code specific to my app
next();
}
I hope this helps someone stuck with the similar issue.
If you want to send a parameter with a query parameter you can use that syntax like that
this.$router.push({
path: this.localePath(`/bookings/${requestReservation?.attributes?.booking_id}`),
query: { requestReservation: requestReservation }
})
You can access it on the next page like that
this.$route.query.requestReservation
If you want send it fro nuxt-link than its syntax like that
<nuxt-link
:to="{ path: '/bookings/'+ requestReservation.attributes.booking_id,
query: { requestReservation } }">
Booking
</nuxt-link>
You can access it on the next page same like previous
this.$route.query.requestReservation

multiple login routes using ember-cli-simple-auth

I am trying to configure a basic ember-cli app using authentication via ember-cli-simple-auth and want to have a dedicated 'guest' login page and a different 'admin' login page (authorizing to different serverTokenEnpoint's).
I have the 'guest' page working, i.e. if a user tries to browse to a protected route (page) then they are redirected to the default /login route and can login Ok.
What I can't figure out is how to have a user that browse's to /admin/xyz route that they then get redirected (using to /admin/login which in turn will authenticate to a different serverTokenEnpoint to the default.
Can anyone point me in the right direction to achieve the above?
Thanks.
an example protected 'guest' route file looks like:
FILE: /app/routes/protected.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin);
And the environment configs contain:
FILE: /app/config/environment.js
ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:oauth2-bearer',
store: 'simple-auth-session-store:local-storage',
crossOriginWhitelist: ['http://www.domain.com/token',
'http://www.domain.com'
]
};
I even tried to override the default authenticationRoute in my /app/routes/admin.js file like below but did not work:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
authenticationRoute: 'admin.login'
});
So to simplify the process following Marco's suggestion I now have:
Note: AT the moment this does not work.. #marcoow do you have any thoughts where im going wrong?
This is using ember-cli with the follow firebug output:
AuthenticatorBase A (unknown mixin) ***<- IS this expected????***
CustomAuthenticator B (unknown mixin)
DEBUG: -------------------------------
DEBUG: Ember : 1.7.0
DEBUG: Ember Data : 1.0.0-beta.9
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.11.1
DEBUG: Ember Simple Auth : 0.6.4
DEBUG: Ember Simple Auth OAuth 2.0 : 0.6.4
DEBUG: -------------------------------
and if I put my manual override code back in see previous answer it will work, but since I want to use the same oauth2 authentication just to a different URL, I like the idea of just being able to override the TokenEndpoint with a custom authenticator.
file: app/initializers/simple-auth-admin.js
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: AppchatENV['simple-auth-admin'].serverTokenEndpoint,
serverTokenRevokationEndpoint: AppchatENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: AppchatENV['simple-auth-admin'].refreshAccessTokens
});
console.log("AuthenticatorBase A ",AuthenticatorBase);
console.log("CustomAuthenticator B ",CustomAuthenticator);
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
}
};
But the above shows an error of "AuthenticatorBase A (unknown mixin)"
and then in
file: app/controllers/admin/login.js
import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin'
}
and for the configs...
file: config/environment.js
ENV['simple-auth-admin'] = {
serverTokenEndpoint: "http://www.domain.com/admintoken",
serverTokenRevokationEndpoint: "http://www.domain.com/admintoken/revoke",
refreshAccessTokens: true
};
EDIT:
so by setting: in file: app/initializers/simple-auth-admin.js
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: MyappENV['simple-auth-admin'].serverTokenEndpoint,
serverTokenRevokationEndpoint: MyappENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: MyappENV['simple-auth-admin'].refreshAccessTokens
});
console.log("AuthenticatorBase.serverTokenEndpoint =",AuthenticatorBase.serverTokenEndpoint);
console.log("CustomAuthenticator.serverTokenEndpoint =",CustomAuthenticator.serverTokenEndpoint);
console.log("MyappENV['simple-auth-admin'].serverTokenEndpoint = ",MyappENV['simple-auth-admin'].serverTokenEndpoint);
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
console.log("[at container.register] CustomAuthenticator.serverTokenEndpoint = ",CustomAuthenticator.create().get('serverTokenEndpoint'));
}
};
I get output of:
AuthenticatorBase.serverTokenEndpoint = undefined
CustomAuthenticator.serverTokenEndpoint = undefined
MyappENV['simple-auth-admin'].serverTokenEndpoint = http://www.domain.com/oauth2/admintoken
[at container.register] CustomAuthenticator.serverTokenEndpoint = http://www.domain.com/oauth2/admintoken
Am I misuderstanding what AuthenticatorBase.extend () is doing? I thought it would allow you to override some variables or functions?
EDIT 2:
file: app/controllers/admin/login.js
import Ember from 'ember';
var $ = Ember.$;
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin',
init: function(){
console.log('INIT LOGIN CONTROLLER', this.get('session'));
this._super();
},
actions: {
authenticate: function() { // (data)
console.log("LoginController clicked");
$('#nameBtn').ladda().ladda('start');
console.log(this.get('session'));
console.log('this.authenticator = ', this.authenticator);
var _this = this;
this._super().then(null, function(data) {
console.log('LOGIN GOT BACK: ', data);
$('#nameBtn').ladda().ladda('stop');
if(data.error !== undefined && data.error !== "") {
_this.set('data', {error: data.error});
}
});
}
}
});
This results in an ajax to www.domain.com/token rather than the expected www.domain.com/admintoken
OK, after a lot of coding in circles and trial and error and with a lot of help from:
https://github.com/simplabs/ember-simple-auth/blob/master/examples/6-custom-server.html
this is how I achieved what I wanted...
1) Setup the enpoints as variables in the environment file (simple-auth-admin is the name i chose for my admin authenticator)
File: /app/config/environment.js
ENV['simple-auth-admin'] = {
serverTokenEndpoint: "http://www.domain.com/admintoken",
serverTokenRevokationEndpoint: "http://www.domain.com/admintoken/revoke",
refreshAccessTokens: true
};
2) Create the actual authenticator as an override in an initialiser Note: in this case the CustomAuthorizer is not actually used and make sure you replace AppNameENV with your app name, so if your app was called bob it would be BobENV.
File: /app/initializers/simple-auth-admin.js
import Ember from 'ember';
import AuthenticatorBase from 'simple-auth/authenticators/base';
import AuthorizerBase from 'simple-auth/authorizers/base';
var CustomAuthorizer = AuthorizerBase.extend({
authorize: function(jqXHR, requestOptions) {
if (this.get('session.isAuthenticated') && !Ember.isEmpty(this.get('session.token'))) {
jqXHR.setRequestHeader('Authorization', 'Token: ' + this.get('session.token'));
}
}
});
var CustomAuthenticator = AuthenticatorBase.extend({
tokenEndpoint: window.AppNameENV['simple-auth-admin'].serverTokenEndpoint,
tokenRevokationEndpoint: window.AppNameENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: window.AppNameENV['simple-auth-admin'].refreshAccessTokens,
init: function(){
console.log("CUSOTMM AUTH INIT ",window.AppNameENV['simple-auth-admin'].serverTokenEndpoint);
this._super();
},
restore: function(data) {
console.log('AdminAuth - restore');
return new Ember.RSVP.Promise(function(resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(credentials) {
console.log('AdminAuth - authenticate',credentials);
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ grant_type: 'password', username: credentials.identification, password: credentials.password, session: { identification: credentials.identification, password: credentials.password } }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.access_token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
console.log('AdminAuth - invalidate');
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
resolve();
})
});
}
});
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
console.log("OVERRIDES : ", window.AppNameENV['simple-auth-admin']);
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
container.register('simple-auth-authorizer:admin', CustomAuthorizer);
}
};
3) The I setup a redirect route to admin/login for any protected pages ( this example is for /admin/dashboard)
File: /app/routes/admin/dashboard.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
authenticationRoute: 'admin.login',
actions: {
authenticateSession: function() {
this.transitionTo(this.authenticationRoute);
}
}
});
4) Then configure the admin controller to use the new custom authenticator
File: /app/controllers/admin/login.js
import Ember from 'ember';
var $ = Ember.$;
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
//import Session from 'simple-auth/session';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin',
});
All of which seems a bit heavy handed when all i really wanted to do was have the authentication for the /admin/login point to a different serverendpoint. Marco, is there a way of overriding just those variables and therefore extend the simple-auth-oauth2 authorizer?
The routes you're defining are necessary of course.
Since your authorizer and authenticator for the admin area seem to be customized as well those are necessary as well of course. If you used the plain OAuth 2.0 ones for the admin area as well you could drop the authorizer and change the authenticator to
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: 'http://www.domain.com/admintoken',
serverTokenRevokationEndpoint: 'http://www.domain.com/admintoken/revoke'
});
Every time Ember Simple Auth enforces authentication (usually when a user accesses an authenticated route while the session is not authenticated), it calls the ApplicationRouteMixin's authenticateSession action. The best option you have is to override that and somehow decide whether to transition to the admin or the guest login page from there. If you have e.g. your admin pages namespaces in an /admin route, you could also override the authenticateSession on the AdminRoute and transition to the admin login page from there while the default implementation in the ApplicationRoute transitions to the guest login page.
For the authenticators it's probably best to use the default OAuth 2.0 authenticator with its serverTokenEndpoint to authenticate guests and extend another authenticator from that that authenticates admin against a different serverTokenEndpoint.

Multiple start pages in Durandal

I have included my main.js and shell.js below for reference. As you can see my default route in the shell.js is the viewmodels/search and it has a second route to viewmodels/application with can take an option parameter, which is the IDKey for a particular application. Most of the time this is how I want users to enter the system by starting with the search screen where they can search for a particular application or have the option to click a button to start a new application. However I would like to be able to publish url links that could skip the search page and start the application with the viewmodels/application page with the appropriate IDKey.
I just cannot seem to figure out how to implement this behaviour. Can anybody get me pointed in the right direction of how to implement this.
MAIN.JS
define('jquery', [], function () { return jQuery; });
define('knockout', [], function () { return ko; });
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], function (system, app, viewLocator) {
app.title = 'My App';
//specify which plugins to install and their configuration
app.configurePlugins({
router: true,
dialog: true,
widget: {
kinds: ['expander']
}
});
app.start().then(function () {
toastr.options.positionClass = 'toast-bottom-right';
toastr.options.backgroundpositionClass = 'toast-bottom-right';
viewLocator.useConvention();
app.setRoot('viewmodels/shell', 'entrance');
});
});
SHELL.JS
define(['plugins/router'], function (router) {
return {
router: router,
activate: function () {
return router.map([
{ route: '', moduleId: 'viewmodels/search', title: 'Permit Application Search', nav: true },
{ route: 'application(/:id)', moduleId: 'viewmodels/application', title: 'Permit Application', nav: true }
]).buildNavigationModel()
.activate();
}
};
});
Following your routes as shown in code, you should simply be able to publish a link like http://yourdomain.com#application/12

"Route Not Found" in console window when Durandal is trying to load first page of app

I'm getting "Route not found" in the console window on trying to load an app converted from 1.2 to 2.0. Is there any way I can debug what route it's trying to find at the point of failure please? It would be handy if it said, "cannot find route:/viewmodels/wrongfolder/startup" or something!
Please be aware that ALL of this was working perfectly prior to upgrading from 1.2 to 2.0, so it's differences in the Durandal settings that I need to address. No files have been removed or lost or moved, so it's not that things have changed in the app outside of the new versions of scripts being updated by nuget.
main.js and config.js live in root of "app" folder. Shell.js is in app/viewmodels and shell.html is in app/views. All views/viewmodels are in the relevant folders below the main /app folder.
I have a "config.js" file with routes returned:
var routes = [{
route: 'home',
moduleId: 'home',
title: 'Home',
nav: true
}, {
route: 'labTool',
moduleId: 'labTool',
title: 'Lab Tool',
nav: true
}];
var startModule = 'labTool';
main.js:
//specify which plugins to install and their configuration
app.configurePlugins({
router: true,
dialog: true,
widget: false
});
app.start().then(function () {
viewLocator.useConvention();
router.makeRelative({ moduleId: 'viewmodels' });
app.setRoot('viewmodels/shell');
router.handleInvalidRoute = function (route, params) {
logger.logError('No route found', route, 'main', true);
};
});
Shell.js:
var inEditMode = ko.observable(false); //set edit mode to false at start
var shell = {
activate: activate,
router: router,
inEditMode: inEditMode
};
return shell;
function activate() {
return datacontext.primeData()
.then(boot)
.fail(failedInitialization);
}
function boot() {
logger.log('Application Loaded!', null, system.getModuleId(shell), true);
router.map(config.routes).buildNavigationModel();
return router.activate(config.startModule);
}
function failedInitialization(error) {
var msg = 'App initialization failed: ' + error.message;
logger.logError(msg, error, system.getModuleId(shell), true);
}
Some of the code may still need editing to handle the change from 1.2 to 2.0 but I think I have most of it now.
I had a similar problem after the upgrade and creating a default route with a route property of '' sorted it for me.
So instead of using your startModule property try setting you labTool route to have a route property of ''.
In case anyone else runs into this, this error can also occur if you have non-ascii characters in the route name.
Not working:
{ route: 'Møøse', ... }
Working:
{ route: 'Moose', title: 'Møøse', ... }