Ember.js - How can I save data to session from authenticator? - session

I have tried doing this.get('session') but it gives me nothing.
I want to save data to my session
I only seem to get the information I need from the authenticator but can't seem to be able to pass it around. (Tried a couple of methods suggested on SO but none seem to be able to work from the autheticator)
import Ember from 'ember';
import Torii from 'ember-simple-auth/authenticators/torii';
const { service } = Ember.inject;
export default Torii.extend({
torii: service('torii'),
authenticate(options) {
return this._super(options).then(function (data) {
console.log(data);
});
}
});
Caller of the autheticator (Is the info I need accessible from here already?)
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticateSession() {
this.get('session').authenticate('authenticator:torii', 'google-token');
},
invalidateSession() {
this.get('session').invalidate();
}
}
});

Your authenticator's authenticate method does not resolve with anything. Change it to
import Ember from 'ember';
import Torii from 'ember-simple-auth/authenticators/torii';
const { service } = Ember.inject;
export default Torii.extend({
torii: service('torii'),
authenticate(options) {
return this._super(options).then(function (data) {
console.log(data);
return data;
});
}
});
to have all attributes in data available via the session's data.authenticated property, e.g. this.get('session.data.authenticated.token').
Of course in this case you can just remove the overridden authenticate method completely if you don't need the logging.

Related

Vuex-ORM GraphQL installation troubles

I installed the Vuex-ORM Graphql Plugin into an existing Nuxt project with Laravel/GraphQL API, so that I could try avoiding using the Apollo Cache. In one of my components though, I'm running:
<script>
import Notification from '~/data/models/notification';
export default {
computed: {
notifications: () => Notification.all()
},
async mounted () {
await Notification.fetch();
}
}
</script>
however I'm receiving the error [vuex] unknown action type: entities/notifications/fetch.
I looked through the debug log and found several available getters (entities/notifications/query, entities/notifications/all, entities/notifications/find, and entities/notifications/findIn). I tried running await Notification.all() in the mounted method which removed the error, however looking in Vuex the Notifications data object is empty.
Here is the rest of my setup:
nuxt.config.js
plugins: [
'~/plugins/vuex-orm',
'~/plugins/graphql'
],
plugins/vuex-orm.js
import VuexORM from '#vuex-orm/core';
import database from '~/data/database';
export default ({ store }) => {
VuexORM.install(database)(store);
};
plugins/graphql.js
/* eslint-disable import/no-named-as-default-member */
import VuexORM from '#vuex-orm/core';
import VuexORMGraphQL from '#vuex-orm/plugin-graphql';
import { HttpLink } from 'apollo-link-http';
import fetch from 'node-fetch';
import CustomAdapter from '~/data/adapter';
import database from '~/data/database';
// The url can be anything, in this example we use the value from dotenv
export default function ({ app, env }) {
const apolloClient = app?.apolloProvider?.defaultClient;
const options = {
adapter: new CustomAdapter(),
database,
url: env.NUXT_ENV_BACKEND_API_URL,
debug: process.env.NODE_ENV !== 'production'
};
if (apolloClient) {
options.apolloClient = apolloClient;
} else {
options.link = new HttpLink({ uri: options.url, fetch });
}
VuexORM.use(VuexORMGraphQL, options);
};
/data/adapter.js
import { DefaultAdapter, ConnectionMode, ArgumentMode } from '#vuex-orm/plugin-graphql';
export default class CustomAdapter extends DefaultAdapter {
getConnectionMode () {
return ConnectionMode.PLAIN;
}
getArgumentMode () {
return ArgumentMode.LIST;
}
};
/data/database.js
import { Database } from '#vuex-orm/core';
// import models
import Notification from '~/data/models/notification';
import User from '~/data/models/user';
const database = new Database();
database.register(User);
database.register(Notification);
export default database;
/data/models/user.js
import { Model } from '#vuex-orm/core';
import Notification from './notification';
export default class User extends Model {
static entity = 'users';
static eagerLoad = ['notifications'];
static fields () {
return {
id: this.attr(null),
email: this.string(''),
first_name: this.string(''),
last_name: this.string(''),
// relationships
notifications: this.hasMany(Notification, 'user_id')
};
}
};
/data/models/notification.js
import { Model } from '#vuex-orm/core';
import User from './user';
export default class Notification extends Model {
static entity = 'notifications';
static fields () {
return {
id: this.attr(null),
message: this.string(''),
viewed: this.boolean(false),
// relationships
user: this.belongsTo(User, 'user_id')
};
}
};
package.json
"#vuex-orm/plugin-graphql": "^1.0.0-rc.41"
So in a Hail Mary throw to get this working, I ended up making a couple of changes that actually worked!
If other people come across this having similar issues, here's what I did...
In my nuxt.config.js, swapped the order of the two plugins to this:
plugins: [
'~/plugins/graphql',
'~/plugins/vuex-orm',
],
In my graphql.js plugin, I rearranged the order of the options to this (database first, followed by adapter):
const options = {
database,
adapter: new CustomAdapter(),
url: env.NUXT_ENV_BACKEND_API_URL,
debug: process.env.NODE_ENV !== 'production'
};

React-Redux re-render on dispatch inside HOC not working

I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.
I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.
My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.
The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.
My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.
It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?
The HomePage HOC:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'
// unconnected stateless react component
const HomePage = (props) => (
<div>
{ !props.auth
? <AnonymousPartialPage />
: <AuthenticatedPartialPage /> }
</div>
);
const mapStateToProps = (state) => {
const store = createStore(
combineReducers({
auth: authReducer
})
);
// When the user logs in, in the Anonymous component, the local storage is set with the response
// of the API when the log in attempt was successful.
const storageAuth = JSON.parse(localStorage.getItem('auth'));
if(storageAuth !== null) {
// Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
store.dispatch(logOutOfApi());
// Make sure the auth info in local storage is contained in the state.auth object.
store.dispatch(loggedIntoApi(...storageAuth))
}
return {
auth: state.auth && state.auth.jwt && storageAuth === null
? state.auth
: storageAuth
};
}
export default connect(mapStateToProps)(HomePage);
with the Anonymous LOC being:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';
export class AnonymousPartialPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
const loginData = { ... };
// This is where I thought the problem initially occurred as I
// would get an error that `this.props` was undefined in the final
// then` of the `fetch`. After doing this, however, the error went
// away and I can see that `props.dispatch is no longer undefined
// when using it. Now though, nothing happens.
const props = this.props;
fetch('https://.../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data && data.jwt) {
props.dispatch(loggedIntoApi(data));
localStorage.setItem('auth', JSON.stringify(data));
}
// else show an error on screen
});
};
render() {
return (
<div>
... onSubmit gets called successfully somewhere in here ...
</div>
);
}
}
export default connect()(AnonymousPartialPage);
the action:
// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
type: 'LOGGED_INTO_API',
auth: auth_token
});
// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
type: 'LOG_OUT_OF_API'
});
and finally the reducer:
const authDefaultState = { };
export default (state = authDefaultState, action) => {
switch (action.type) {
case 'LOGGED_INTO_API':
// SOLUTION : changed this line "return action.auth;" to this:
return { ...action.auth, time_stamp: new Date().getTime() }
case 'LOG_OUT_OF_API':
return { auth: authDefaultState };
default:
return state;
}
};
My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().
Essentially:
x = {
foo:bar
}
x.foo = "baz"
dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!
Instead I created a new object:
x = {
foo:"bar"
}
y = JSON.parse(JSON.stringify(x)) // creates an entirely new object
dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender
// BE CAREFUL OF THE FOLLOWING!
y = x
dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender
Hope this helps!

will it break the react-redux design to create method which called in other action creator?

I don't know if we should stick to the react-redux style all the time.
With react-redux, we put logic to the action creator, no matter simple action or async request. however, it indeed helps us to keep the work flow simple and clear:dispatch a action, and reducer to process the action to change data in store.
but I am curious about:
can I create a function which is not an action creator? does this break the redux design?
for example:
action-creator-1.js
const getData=()=>(dispatch,getState)=>{
return Promise.resolve().then(()=>{
//do long-running work
});
};
export default{
getData
};
action-creator-2.js
import action1 from 'action-creator-1';
const loadData=()=>(dispatch,getState)=>{
return Promise.resolve().then(()=>{
//? return action1.getData();
})
.then(()=>{
//do other work
})
}
can we set getData a simple function in action creator file:
const getData=(state)=>{
return Promise.resolve().then(()=>{
//get local data
})
}
can this make sense?
Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().
In action.js
import {serviceCall} from './service-call';
export function showLoader() {
return {
type: 'SHOW_LOADER',
payload: true
};
}
export function loadData() {
Store.dispatch(showLoader());
return {
type: 'SET_WIDGET_DATA',
payload: return Promise.resolve().then(response => {
return response
})
}
}
export function getData() {
Store.dispatch(showLoader());
return {
type: 'GET_PAGE_DATA',
payload: return Promise.resolve().then(response => {
Store.dispatch(loadData());
})
}
}
export function serviceCall() {
Store.dispatch(showLoader());
return {
type: 'GET_HTTP_DATA',
payload: serviceCall({
url: 'http://'
})
}
}
can I create a function which is not an action creator? does this break the redux design? ==> yes this will not break your redux design as we also create another .js file for http call like service.js.

Loading component data asynchronously server side with Mobx

I'm having an issue figuring out how to have a react component have an initial state based on asynchronously fetched data.
MyComponent fetches data from an API and sets its internal data property through a Mobx action.
Client side, componentDidMount gets called and data is fetched then set and is properly rendered.
import React from 'react';
import { observer } from 'mobx-react';
import { observable, runInAction } from 'mobx';
#observer
export default class MyComponent extends React.Component {
#observable data = [];
async fetchData () {
loadData()
.then(results => {
runInAction( () => {
this.data = results;
});
});
}
componentDidMount () {
this.fetchData();
}
render () {
// Render this.data
}
}
I understand that on the server, componentDidMount is not called.
I have something like this for my server:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { useStaticRendering } from 'mobx-react';
import { match, RouterContext } from 'react-router';
import { renderStatic } from 'glamor/server'
import routes from './shared/routes';
useStaticRendering(true);
app.get('*', (req, res) => {
match({ routes: routes, location: req.url }, (err, redirect, props) => {
if (err) {
console.log('Error', err);
res.status(500).send(err);
}
else if (redirect) {
res.redirect(302, redirect.pathname + redirect.search);
}
else if (props) {
const { html, css, ids } = renderStatic(() => renderToString(<RouterContext { ...props }/>));
res.render('../build/index', {
html,
css
});
}
else {
res.status(404).send('Not found');
}
})
})
I have seen many posts where an initial store is computed and passed through a Provider component. My components are rendered, but their state is not initialized. I do not want to persist this data in a store and want it to be locally scope to a component. How can it be done ?
For server side rendering you need to fetch your data first, then render. Components don't have a lifecycle during SSR, there are just render to a string once, but cannot respond to any future change.
Since your datafetch method is async, it means that it cannot ever affect the output, since the component will already have been written. So the answer is to fetch data first, then mount and render components, without using any async mechanism (promises, async etc) in between. I think separating UI and data fetch logic is a good practice for many reasons (SSR, Routing, Testing), see this blog.
Another approach is to create the component tree, but wait with serializing until all your promises have settled. That is the approach that for example mobx-server-wait uses.

How do I get attributes of Custom Session using Ember Simple Auth

PROBLEM: I don't know how to get the current session in a controller.
I have a custom authenticator, custom session, and initializer defined like so:
CUSTOM AUTHENTICATOR in ../app/authenticators/custom.js
var CustomAuthenticator = Base.extend({
authenticate: function(credentials) {
return new Ember.RSVP.Promise(function (resolve, reject){
var loginPromise = Ember.$.post('/api/login', {'email':credentials.identification, 'password':credentials.password} );
loginPromise.then(function (data){
resolve({
token: data.user.api_key,
userData: data.user
});
}, function(error){
reject(error);
});
});
}
});
CUSTOM SESSION in ../app/sessions/custom.js
import Ember from 'ember';
import Session from 'simple-auth/session';
var CustomSession = Session.extend({
after:'simple-auth',
currentUser: function(){
return this.container.lookup('ember_simple_auth:session');
}.property('currentUser')
});
export default CustomSession;
INITIALIZER in ../app/initializers/authentication.js
import CustomAuthenticator from '../authenticators/custom';
import CustomSession from '../sessions/custom';
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container) {
container.register('authenticator:custom', CustomAuthenticator);
container.register('session:custom', CustomSession);
}
};
I'm trying to get the token and userData in one of my controllers by using this.get('session') but it's giving me the following:
Class {store: Class, __ember1420041799205: "ember297", __nextSuper: undefined, __ember_meta__: Object, constructor: function…}
and I see the ember_simple_auth:session key and values in the local browser storage {"authenticator":"authenticator:custom","token":"123456789","userData":{"id":"1","email":"something#email.com","api_key":"123456789","expiry_time":"2014-12-31 14:02:56"}}
I basically need to get what's in the local storage. How do I do this?
Ah, I figured out the problem. When first authenticating, the session variable was there but refreshing the page got rid of the session's content because I did not have a restore function in my authenticator.
restore: function(data) {
return new Ember.RSVP.Promise(function (resolve, reject){
console.log('RESTORE');
if(!Ember.isEmpty(data.token)) {
console.log('Found token: ' + data.token);
resolve(data);
} else {
console.log('Token Not Found!');
reject();
}
});
}

Resources