Observing Store State Changes in Middleware - redux

I'm writing a piece of Redux Middleware which is responsbile for adding the user's OAuth AccessToken to API_CALL cations before they hit the redux-api-middleware.
// sign appends the `Authorization` HTTP Header to an API_CALL action
function sign(action) {
action[CALL_API].headers = {
...action[CALL_API].headers,
Authorization: `Bearer ${getState()auth.accessToken}`;
}
}
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action[CALL_API]) {
sign(action);
}
return next(action);
}
}
However, I also want this middleware to detect when the User's AccesToken has expired...
function tokenExpired() {
return (Date.now() > getState().auth.expirationTime);
}
When this happens, the middleware detains the action (prevents it from being passed to the next middleware in the chain) and stores it in an internal list. It then kicks off the async. Token refresh process by dispatching a 'refresh access token' FSA:
if (tokenExpired()) {
// detainActions is an array, declared outside
// of the middleware's scope.
detainActions.push(action);
dispatch(refreshAccessToken());
}
else {
next(sign(action));
}
Finally, I want to listen for when the 'refresh access token' flow has completed and flush (re-dispatch) all detained actions. Currently I am doing this by looking out for AUTHENTICATION_RESPONSE FSA's as they flow through my middleware (the AUTHENTICATION_RESPONSE action is dispatched as as side-effect of the refreshAccessToken thunk.
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action.type === AUTHENTICATION_RESPONSE && !action.error) {
// Let the AuthResponse action pass to the store.
next(action);
// Flush detained actions now we have a new grant.
return flushAndRedispatchDetainedActions(dispatch);
}
else {
// Sign CALL_API requests logic as above.
}
}
}
However I am not happy with this approach as there is no certainty that the AUTHENTICATION_RESPONSE FSA will actually hit the reducers (it may be intercepted by other middleware, or be further defered).
Possible alternative approaches I have considered are having the refreshAccessToken actionCreator return a thunk which returns a Promise; that way my middleware can wait for that promise to resolve before flushing and replaying all requests, ie:
if (tokenExpired()) {
// refreshAccessToken thunk returns a Promise.
dispatch(refreshAccessToken());
.then(flushAndRedispatchDetainedActions();
}
Or alternativley I could have my middleware observe the store directly and trigger an action when the auth.accessToken value changes - however, I'm not clear on what the guidance is for middleware observing the store (I'm guessing it's not possible as middleware needs to be instantiated before the final store object is created.)
Thanks :)
Update
Thinking about the problem on the way home; if I were to move the actual authentication logic out of refreshAccessToken (a thunk'd action creator), and into the middleware itself then I would save a lot of pain:
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action[CALL_AUTH]) {
authHelper.refreshGrant()
.then(grant => dispatch(processGrant(newGrant));
}
else {
// Sign CALL_API requests logic as above.
}
}
}

Related

restarting a queue of API requests if a token refresh happened

I'm having a hard time wrapping my brain around this pattern I am trying to implement so I'm hoping the stack overflow community might be able to help me work through a solution to this.
Currently I use redux-thunk along with superagent to handle calls to me API and syncing it all up with redux
An example of this might look like
export const getUser = (id) => {
return (dispatch) => {
const deferred = new Promise((resolve, reject) => {
const call = () => {
API.get(`/users/${id}`)
.then((response) => response.body)
.then((response) => {
if (response.message === 'User found') {
serializeUser(response.data).then((response) => {
resolve(response);
});
} else {
reject('not found');
}
}).catch((err) => {
handleCatch(err, dispatch).then(call).catch(reject)
});
}
call()
});
return deferred;
};
};
In the case where the server comes back with a 200 and some data I continue on with putting the data into the store and rendering to the page or whatever my application does.
In the case I receive an error I have attempted to write a function that will intercept those and determine if it should show an error on page or in the case of a 401 from our API, attempt a token refresh and then try to recall the method...
import { refreshToken } from '../actions/authentication';
export default (err, dispatch) => {
const deferred = new Promise((resolve, reject) => {
if (err.status === 401) {
dispatch(refreshToken()).then(resolve).catch(reject)
} else {
reject(err);
}
})
return deferred;
};
This works, however, I have to add this to each call, and it doesn't account for concurrent calls that should not attempt to call if there is a refresh in progress.
I've seen some things in my research on this topic that maybe redux-saga could work but I haven't been able to wrap my brain around how I might make this work
Basically, I need something like a queue that all my API requests will go into that is maybe debounced so any concurrent requests will just be pushed to the end and once a timeout ends the calls get stacked up, when the first call gets a 401 it pauses the queue until the token refresh either comes back successful, in which case it continues the queue, or with a failure, in which case it cancels all future requests from the queue and sends the user back to a login page
The thing I would be worried about here is if the first call in the stack takes a long time, I don't want the other calls to then have to wait a long time because it will increase the perceived loading time to the user
Is there a better way to handle keeping tokens refreshed?

Redux middleware not dispatching new actions

I'm creating a Redux middleware that listens for a specific action. If that action type matches what I'm looking for, I want to dispatch another action. The reason for this is because I have different components with some shared functionality, so I want to update actions to have similar types, but different payloads (term), like so:
const updateActions = store => next => action => {
console.log(action);
if (action.type === 'UNIQUE_ACTION_A') {
return next({ type: 'NEW_ACTION', term: 'new_action_a_test' });
} else if (action.type === 'UNIQUE_ACTION_B') {
return next({
type: 'NEW_ACTION',
term: 'new_action_b_test'
});
}
return next(action);
};
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk, updateActions))
);
The problem I'm having is that these actions are not dispatching. When I console.log all actions, they continue to run as normal, rather than dispatching the new actions. It's as if the call to dispatch these actions are just being ignored. What am I doing incorrectly here? Thanks!
There's a difference between next and dispatch inside middleware. dispatch sends an action to the very start of the dispatch chain, which will cause it to run through all the middleware in the pipeline. next sends it to the next middleware after this one, and eventually to the reducers. By calling next({type : "NEW_ACTION"}), you're sending it to the next middleware, and this middleware will never see "NEW_ACTION".
Also see the Redux FAQ entry on next vs dispatch in middleware.

rxjs - How to retry after catching and processing an error with emitting something

I'm using rxjs v5.4.3, redux-observable v0.16.0.
in my application, I'd like to achieve below:
an user has auth token, and refresh token to regenerate auth token.
the user requests with auth token. (by emitting REQUEST action)
if it failed, request regenerating auth token with refresh token.
if refreshed, emit TOKEN_REFRESHED action to update auth token, and do not emit REQUEST_FAILURE.
if refreshing failed, emit REQUEST_FAILURE
after refreshing(and updating auth token reducer), retry requesting using the refreshed auth token.
if request succeeded, emit REQUEST_SUCCESS, and if failed, emit REQUEST_FAILURE.
I'd like to achieve like:
const fetchEpic = (action$: ActionsObservable<Action>, store: Store<IRootState>) => action$
.ofAction(actions.fetchPost)
.mergeMap(({ payload: { postId } })) => {
const { authToken, refreshToken } = store.getState().auth;
return api.fetchPost({ postId, authToken }) // this returns Observable<ResponseJSON>
.map(res => actions.fetchSuccess({ res })) // if success, just emit success-action with the response
.catch(err => {
if (isAuthTokenExpiredError(err) {
return api.reAuthenticate({ refreshToken })
.map(res => actions.refreshTokenSuccess({ authToken: res.authToken });
.catch(actions.fetchFailure({ err }))
// and retry fetchPost after re-authenticate!
}
return Observable.of(actions.fetchFailure({ err }))
})
}
is there any solution?
There are many ways to do it, but I would recommend splitting off the reauthentication into its own epic to make it easier to maintain/test/reuse.
Here's what that might look like:
const reAuthenticateEpic = (action$, store) =>
action$.ofType(actions.reAuthenticate)
.switchMap(() => {
const { refreshToken } = store.getState().auth;
return api.reAuthenticate({ refreshToken })
.map(res => actions.refreshTokenSuccess({ authToken: res.authToken }))
.catch(err => Observable.of(
actions.refreshTokenFailure({ err })
));
});
We'll also want to use something like Observable.defer so that each time we retry, we look up the latest version of the authToken:
Observable.defer(() => {
const { authToken } = store.getState().auth;
return api.fetchPost({ postId, authToken });
})
When we catch errors in fetchEpic and detect isAuthTokenExpiredError we return an Observable chain that:
Starts listening for a single refreshTokenSuccess, signalling we can retry
Just in case the reauthing itself fails, we listen for it with .takeUntil(action$.ofType(refreshTokenFailure)) so that we aren't waiting around forever--you might want to handle this case differently, your call.
mergeMap it to the original source, which is the second argument of the catch callback. The "source" is the Observable chain before the catch, and since Observables are lazy, when we receive the refreshTokenSuccess action it it will resubscribe to that chain again, effectively be a "retrying"
Merge the above chain with an Observable of an reAuthenticate action. This is used to kick off the actual reauth.
To summarize: the Observable chain we return from catch will first starting listening for refreshTokenSuccess, then it emits reAuthenticate, then when (and if) we receive refreshTokenSuccess we will then "retry" the source, our api.fetchPost() chain above the catch that we wrapped in an Observable.defer. If refreshTokenFailure is emitted before we receive our refreshTokenSuccess, we give up entirely.
const fetchEpic = (action$, store) =>
action$.ofType(actions.fetchPost)
.mergeMap(({ payload: { postId } })) =>
Observable.defer(() => {
const { authToken } = store.getState().auth;
return api.fetchPost({ postId, authToken });
})
.map(res => actions.fetchSuccess({ res }))
.catch((err, source) => {
if (isAuthTokenExpiredError(err)) {
// Start listening for refreshTokenSuccess, then kick off the reauth
return action$.ofType(actions.refreshTokenSuccess)
.takeUntil(action$.ofType(refreshTokenFailure))
.take(1)
.mergeMapTo(source) // same as .mergeMap(() => source)
.merge(
Observable.of(action.reAuthenticate())
);
} else {
return Observable.of(actions.fetchFailure({ err }));
}
});
);
These examples are untested, so I may have some minor issues but you hopefully get the gist. There's also probably a more elegant way to do this, but this will at least unblock you. (Others are more than welcome to edit this answer if they can decrease the complexity)
Side notes
This creates the slight potential for infinite retries, which can cause nasty issues both in the person's browser or your servers. It might be a good idea to only retry a set number of times, and/or put some sort of delay in between your retries. In practice this might not be worth worrying about, you'll know best.
You (or someone else reading this later) may be tempted to use .startWith(action.reAuthenticate()) instead of the merge, but be mindful that a startWith is just shorthand for a concat, not a merge, which means it would synchronously emit the action before we have started to listen for a success one. Usually that isn't an issue since http requests are async, but it's caused people bugs before.

Handling Side-Effects in Async (thunk'd) Actions

I have an async actionCreator which handle my app's authentication flow:
function createAuthenticationResponse(err, grant) {
return {
type: AUTHENTICATION_RESPONSE,
payload: err || grant,
error: Boolean(err)
}
}
function authenticate() {
// return a thunk.
return dispatch => {
// Notify the system that we are authenticating.
dispatch({ type: AUTHENTICATE });
// Trigger the auth flow.
myAuthModule.authorize((err, grant) => {
// Trigger a state-change on the outcome.
dispatch(createAuthenticationResponse(err, grant));
// Q: How do I handle this side-effect?
if (!err) {
dispatch(extractUserInfo(grant));
}
});
};
}
My actionCreator contains business logic to extract the user info from the grant if the user was succesfully authenticated; should this logic live in my action creator? If not, where should I place it, inside my reducer?
In other architectures I would bind a command to trigger on AUTHENTICATION_RESPONSE; but this doesn't feel like a middleware job?
I think what you're suggesting is totally sensible.
You can use Redux Thunk both for control flow and side effects.
You should never put side effects into the reducers.

Play2 async when using action composition

In Play2, I understand the concepts of action composition and using Async {...} for asynchronous responses, but I've not seen an example of these approaches used together.
To be clear, say you're using action composition to ensure that a user is authenticated:
def index = Authenticated { user =>
Action { request =>
Async {
Ok("Hello " + user.name)
}
}
}
In the implementation of Authenticated, if we assume that this a DB is being looked up to retrieve a user, then it appears to me that this part would be a blocking call, leaving only the response within the Action body as non-blocking.
Can somebody explain how I can do non-blocking async I/O that encompasses the authentication part as well?
My solution has been to compose actions inside out, like so:
def index = Action { request =>
Async {
Authenticated { user =>
Ok("Hello " + user.name)
}
}
}
My Authenticated signature would then look something like:
def Authenticated(body: => Result): Result = {
// authenticate, and then
body
}
The downside here is that body parsing will occur before authentication, which may or may not be an issue for you.
I can give a full example: http://pastebin.com/McHaqrFv
The essence of it is:
def WithAuthentication(f: WithAuthentication => Result) = Action {
implicit request => AsyncResult {
// async auth logic goes here
}
}
In controllers simply:
def indexAction = WithAuthentication {
implicit request =>
Ok(views.html.index())
}
or even:
def someAction = WithAuthentication {
implicit request => Async {
// more async logic here
}
}
It's a template-friendly solution, feel free to use it. Maybe sometimes i'll create a plugin from it. Just coded for my application, so far it seems working.

Resources