I am just getting started with Durandal.js so excuse me for the silly quesstion...
When a user makes it's first request to the app it is asked to choose a 'profile kind', and I need it to be accessible to every other view model in the web site, I first though of creating this property in the shell viewmodel, but don't how to do it.
How is the best way to store data in a Session like mode in a Durandal SPA?
Thanks!
Create an amd module for what data you need to store.
Then just require that module as a dependency for whatever other modules that need it.
Sort of like this:
session module
define(function () {
return {
someVariable: 'value1',
someVariable2: 'value2'
}
})
some other module
define(['session'], function(session) {
return {
getValue1: function () {
return session.someVariable;
},
obs1: ko.observable(session.someVariable2)
}
})
EDIT**
AMD modules are there to not pollute the global namespace of the window object. But if you would rather not require your session as a dependency and just access it through a global variable then that is perfectly fine.
you can declare it in your shell.js if you would like and do something like:
define(function () {
window.session = { someVariable: 'value1', someVariable2: 'value2' };
})
then inside some other module you can access the session object like so:
define(function() {
return {
getValue1: function () {
return session.someVariable;
},
obs1: ko.observable(session.someVariable2)
}
})
This information will not be persisted between page refreshes.. its only in-memory.
If your looking to persist the session data I would not look into persisting any information on the client unless you planned on making your app an off-line application.
An offline application is an app that works even w/out internet access. But if your app requires that the user is always connected to the internet then I would just store the session data on the server. So, just use web services to persist the session data and retrieve it.
You can tie the session on the server to the client by using a cookie.
As an alternative to Evan's answer, which is definitively the correct AMD approach... have you considered using a global object for that purpose?
in main.js
window.myApp = {
prop1: 'value',
subOne: {
prop1: 'value'
}
...
}
That will allow you to access the global myApp object everywhere. I know that some people will consider that as global namespace pollution and in general a bad practice, but in a SPA project (where you control window) I'd consider this still a viable approach.
Related
I have a Nuxt app with authentication already running in universal mode.
I'm trying to convert the authentication service to Auth0. I'm following the Vue quickstart, but I discovered that auth0-js is a client side library since it uses a lot of 'window'-stuff that is not available on the server-side of Nuxt.
However, I got it kind of working by making it a client-side plugin and wrap all functions (that is calling the authservice in the lifecycle hooks) in a process.client check. It works "kind of" because when going to the protected page whilst not logged in, it flashes the page before being redirected to login page (since its rendered on the server-side as well, but the check only happens once it's delivered on the client side I presume).
My question now is: What can I do in order to add the check to server-side as well? (or at least make sure that the protected pages isn't flashed before being redirected).
What I've tried so far:
Saving the payload and the logged-in state in the store and check in some custom middleware, but that didn't do the trick.
Also, it seems to me that #nuxt/auth is outdated or something and the nuxt auth0 example as well. It uses auth0-lock while I'm using the new auth0 universal.
Anyone have suggestions on how to solve this issue? Thanks in advance!
not sure if this will be any help and have only answered a few questions (other account long time ago).
Update.. I read my answer then the question title (I think my answer does cover some of your context), but in regards to the title you could also look at using auth as a plugin. You can then handle stuff there before the page is hit.
I am not sure how your code is implemented, but this may help (hopefully).
If you are not using Vuex, I strong recommend it. Nuxt Vuex Store Guide
// index/store.js
// At least have the store initialized, but its most likely going to be used..
// page.vue
<template>
...
<div v-else-if="!$auth.loggedIn">
{{ test }}
</div>
...
...
data() {
if (!this.$auth.loggedIn) {
const test = 'Only this will load, no flash'
return { test }
}
}
$auth.loggedIn is built in, I read it ..somewhere.. in the docs
This will solve the no flash issue, you can also take advantage of a loader screen and asyncData to check the state before rendering the view to avoid a flash and populate data if it hangs.
You could also try using Vuex Actions, I am currently playing with these 2 in the process of where I am now. Learning about nuxtServerInit()
// store/index.js
import axios from 'axios'
export const actions = {
nuxtServerInit ({commit}, {request}) {
// This is good if you have the user in your request or other server side stuff
if (request.user) commit('SET_USER', request.user)
},
async GET_USER({ commit }, username) {
const user = await axios.get(`/user/${username}`)
if (user) commit('SET_USER', user)
}
}
export const mutations = {
SET_USER(state, user) {
// simple set for now
state.auth.user = user || null
}
}
The second one is combined using the fetch() method on the page itself.
// page.vue
async fetch({ $auth, store }) {
await store.dispatch('GET_USER', $auth.$state.user)
}
Now you can call $auth.user in your code as needed.
$auth.user is another built in I read ..somewhere..
You can also call $auth.user with the $auth.loggedIn to check if user exists on top of being logged in $auth.user && $auth.loggedIn.
It may be this.$auth.<value> depending on where you are trying to reference it.
I learned the asyncData() gets call first and logs in my server, then data() logs values in the server console as well (false, null), but in my Brave console they're undefined, i'd like an answer to that lol
I have been struggling with trying to get Auth0 to work how I wanted with JWTs, but as I kept crawling I found useful bits along the way (even in old demos such as the one you mentioned, just nothing with the lock stuff...). Also in terms of express and my API in general... Anyways, hope this helped (someone).
I am having trouble getting dependency injection working for my AuthorizerService. Obviously, dep-inj is not ready until after Aurelia "starts", but I wasn't sure how to access it.
main.js:
aurelia.container.registerInstance(HttpClient, http.c());
// set your interceptors to take cookie data and put into header
return aurelia.start().then(() => {
let Authorizer = new AuthorizerService();
aurelia.container.registerInstance(AuthorizerService, Authorization);
console.log('Current State: %o', Authorizer.auth);
Authorizer.checkCookieAndPingServer().then(() => { console.log('Current State: %o', Authorizer.auth); aurelia.setRoot(PLATFORM.moduleName('app')); }, () => { aurelia.setRoot(PLATFORM.moduleName('login-redirect')); });
});
Now the problem is that if I do "new AuthorizerService()" then "this.http.fetch()" is not available in AuthorizerService.js.
Am I meant to pass "http.c()" (which delivers the HttpClient instance) as a parameter inside:
checkCookieAndPingServer(http.c())
or is there another way?
Can I delete "new AuthorizerService()" and just do (I made this up):
aurelia.container.getInstance(AuthorizerService);
Somehow FORCE it to do dependency-injection and retrieve the "registered Instance" of "http.c()"?
I can't just check cookie. I have to ping server for security and the server will set the cookie.
I think this is all sorts of wrong, because I need a global parameter that just is false by default, then it does the query to backend server and setsRoot accordingly. Perhaps only query backend in the "login page"? Okay but then I would need to do "setRoot(backtoApp); aurelia.AlsoSetLoggedIn(true);" inside login module. But when I setRoot(backtoApp) then it just starts all over again.
In other words, when setRoot(login); then setRoot(backToApp); <-- then AuthorizerService instance doesn't have its proper data set (such as loggedIn=true).
EDIT: Better Solution maybe:
main.js:
return aurelia.start().then(() => {
let Authorizer = aurelia.container.get(AuthorizerService);
let root = Authorizer.isAuthenticated() ? PLATFORM.moduleName('app') : PLATFORM.moduleName('login');
console.log('Current State: %o', Authorizer.auth);
aurelia.setRoot(root);
});
Authorizer.js
constructor(http) {
this.http = http;
this.auth = {
isAuthenticated: false,
user: {}
}
}
"this.auth" is no longer static. No longer "static auth = { isAuthenticated: false }" which was some example code I had found.
So now "auth" gets set inside "login" module. But this means the "login" module is displayed every single time the app loads briefly, before being redirected back to "setRoot(backToApp)"
If the class you want to get the instance is purely based on service classes and has no dependencies on some Aurelia plugins, it doesn't need to wait until Aurelia has started to safely invoke the container.
For your example:
aurelia.container.getInstance(AuthorizerService);
It can be
aurelia.container.get(AuthorizerService);
And you should not use new AuthorizerService(), as you have noticed in your question.
I set the aurelia-store up as per the docs; in the main.ts at the bottom of all the plugins (from the skeleton app with dotnet core) I have as the last plugin defined:
aurelia.use.standardConfiguration()
.plugin(PLATFORM.moduleName('aurelia-store'), { initialState })
Then my app needs to login the user and save their bearer token.
await aurelia.start();
await aurelia.setRoot(PLATFORM.moduleName("modules/login/login.vm"));
In the login class I am trying to use the #connectTo decorator. However it never sets the dependency property. So I am stuck on this simple part at the very start of the app and my work already suggested not to use Aurelia but I said I wanted to for fast POC.
I've copied the docs exactly and still have the issue. Notably, I had to turn off strictNullCheck in the tsconfig to make the doc code parse.
Login.ts
#connectTo({
target: 'state',
selector: {
userToken: (store) => store.state.pipe(pluck('userToken')),
loginRedirected: (store) => store.state.pipe(pluck('loginRedirected'))
}
})
export class Login {
static inject = [Aurelia, Store]
public state: State;
app: Aurelia;
constructor(Aurelia, private store: Store<State>) {
this.app = Aurelia
store.registerAction('ChangeUserToken', this.changeUserToken)
store.registerAction('LoginRedirected', this.loginRedirect)
}
activate() {
... this.state is always undefined.
if (!this.state.loginRedirected) { //error
}
}
}
I expect the this.state property to have a state object populated from the global state store with the initialState values.
e.g.
{ userToken: "", loginRedirected: false }
I just need to set the userToken in login and retrieve it in app.js. This is not possible; what could be missing to make this basic function actually work?
ConnectTo is a helper decorator to avoid manual state subscriptions since the Stream of states is a vanilla rxjs observable. If you take a closer look at the official plugin documentation you will notice that it sets up the subscription in a different lifecycle hook.
That said connectTo cant solve everything and with manual subscription you have the most flexibility.
Dont give up with your quest you just had bad luck of falling into a more complicated scenario of startup timing right at the begin which easy enough might bite you with lots of other Frameworks/Libraries as well. Also make sure to visit the official discourse.aurelia.io forum and post back solutions to SO.
Im currently refactoring an app and converting all my base code into vue. One of my requirements is to do server side rendering.
I have been follow vue ssr example along with hacker news example to help me understand ssr.
I do have however a question for which I cant find any good answer, and before further development, I want to make sure we are doing the right thing.
I want to know if its a good practice to have some actions in a vue store calling an api for server side rendering.
All the examples I have found deal with a real external endpoint they connect and perform request. But that is not the setup we have.
We do have a "normal" express app with its own endpoints; so, for example, express router looks a bit like this:
// This is our post model, it lives on the same app, so its not a external API
app.get('/posts', (req, res) => Posts.getPosts());
// Resolve request for SSR rendering, pretty much the same as in [vue ssr example](https://ssr.vuejs.org/guide/routing.html#routing-with-vue-router)
app.get(
const context = { url: req.url }
createApp(context).then(app => {
renderer.renderToString(app, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found')
} else {
res.status(500).end('Internal Server Error')
}
} else {
res.end(html)
}
})
})
);
This part works fine on both client and server. If you request something to /posts you get your response back.
To have a more modular code, we are using vuex stores; being one of the actions called fetchPosts and this action is the responsible of fetching the current posts in the view.
...
actions: {
fetchPosts({ commit }) {
return $axios.get('/posts').then((response) => {
commit('setPosts', {
posts: response.data
});
});
}
},
...
I believe for client side this is good, but when rendering on the server, this is probably not optimal.
Reason being axios performing an actual http request, which will also have to implement auth mechanism, and in general, really poor performant.
My question is: is this the recommended and standard way of doing this ?
What are other possibilities that works in server and client ?
Do people actually creates separated apps for api and rendering app ?
Thanks !
I know this is an old question, but for future visitors:
The recommended way is to leverage webpack module aliases to load a server side api for the server and a client side api for the browser. You have to share the same call signature which allows the api to be "swapped".
This of course greatly improves performance as the server side api can do direct db queries instead fetching data over http.
In essence your webpack.server.config.js should have:
resolve: {
alias: {
'create-api': './create-api-server.js'
}
}
In your webpack.client.config.js:
resolve: {
alias: {
'create-api': './create-api-client.js'
}
}
Importing create-api will now load the required api.
Have a look at https://github.com/vuejs/vue-hackernews-2.0 to see a full example.
what i'm trying to do is lock down certain pages in my durandal(knockoutjs/breezejs) SPA app based on a user's authenticated role (ie, if you're an admin, etc, you should see pages that others shouldn't).
ideally, server code could be used to either output the link or not based on role, but since durandal and SPA apps don't really work that way with server code, i'm at a loss.
i realize this question has been asked in other forms here (i.e. how to use .cshtml (server) pages with Durandal), and this could be an option if perhaps i could get it to work. i have yet to find a very complete example that works for me.
any ideas appreciated!
You can keep your routing information on the server and supply it to the application on initialization.
For example. In the activate method of your shell viewmodel
shell.js
define(['durandal/system', 'plugins/router', 'durandal/app', 'services/datacontext'],
function (system, router, app, datacontext) {
var routeInfo = ko.observable();
var shell = {
activate: activate,
router: router
};
return shell;
//#region Internal Methods
function activate() {
app.title = "Sample App";
return boot();
}
function boot() {
return datacontext.getRouteInformation(routeInfo).then(function() {
return router.map(routeInfo()).buildNavigationModel().mapUnknownRoutes('error', 'Error').activate();
});
}
//#endregion
});
my datacontext.getRouteInformation() gets a json array of route information based on the current users security context and populates the routeInfo observable. that observable is then passed to the map function to create the users valid routes etc.
I know this doesn't fully "lock down" the html files etc, but all of my controllers and actions have authorization attributes on them, so the data is protected.