mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-22 15:17:53 -05:00
Merge pull request #3576 from benjiwheeler/join-flow-retry-session
Retry fetching session until it succeeds, when registering using join flow
This commit is contained in:
commit
6a97d44978
5 changed files with 115 additions and 25 deletions
|
@ -121,13 +121,16 @@ class JoinFlow extends React.Component {
|
|||
// * "birth_month": ["Ensure this value is greater than or equal to 1."]
|
||||
handleRegistrationResponse (err, body, res) {
|
||||
this.setState({
|
||||
numAttempts: this.state.numAttempts + 1,
|
||||
waiting: false
|
||||
numAttempts: this.state.numAttempts + 1
|
||||
}, () => {
|
||||
const success = this.registrationIsSuccessful(err, body, res);
|
||||
if (success) {
|
||||
this.props.refreshSession();
|
||||
this.setState({step: this.state.step + 1});
|
||||
this.props.refreshSessionWithRetry().then(() => {
|
||||
this.setState({
|
||||
step: this.state.step + 1,
|
||||
waiting: false
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// now we know something went wrong -- either an actual error (client-side
|
||||
|
@ -135,7 +138,10 @@ class JoinFlow extends React.Component {
|
|||
|
||||
// if an actual error, prompt user to try again.
|
||||
if (err || res.statusCode !== 200) {
|
||||
this.setState({registrationError: {errorAllowsTryAgain: true}});
|
||||
this.setState({
|
||||
registrationError: {errorAllowsTryAgain: true},
|
||||
waiting: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,7 +170,8 @@ class JoinFlow extends React.Component {
|
|||
registrationError: {
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: errorMsg
|
||||
}
|
||||
},
|
||||
waiting: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -283,15 +290,15 @@ JoinFlow.propTypes = {
|
|||
createProjectOnComplete: PropTypes.bool,
|
||||
intl: intlShape,
|
||||
onCompleteRegistration: PropTypes.func,
|
||||
refreshSession: PropTypes.func
|
||||
refreshSessionWithRetry: PropTypes.func
|
||||
};
|
||||
|
||||
const IntlJoinFlow = injectIntl(JoinFlow);
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
refreshSession: () => {
|
||||
dispatch(sessionActions.refreshSession());
|
||||
}
|
||||
refreshSessionWithRetry: () => (
|
||||
dispatch(sessionActions.refreshSessionWithRetry())
|
||||
)
|
||||
});
|
||||
|
||||
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
||||
|
|
23
src/lib/session.js
Normal file
23
src/lib/session.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const api = require('./api');
|
||||
|
||||
module.exports = {};
|
||||
|
||||
module.exports.requestSessionWithRetry = (count, resolve, reject) => {
|
||||
api({
|
||||
host: '',
|
||||
uri: '/session/'
|
||||
}, (err, body, response) => {
|
||||
if (err || (response && response.statusCode === 404)) {
|
||||
return reject(err);
|
||||
}
|
||||
if (typeof body === 'undefined' || !body.user) {
|
||||
// Retry after 500ms, 1.5s, 3.5s, 7.5s and then stop.
|
||||
if (count > 4) {
|
||||
return resolve(body);
|
||||
}
|
||||
return setTimeout(module.exports.requestSessionWithRetry.bind(null, count + 1, resolve, reject),
|
||||
250 * Math.pow(2, count));
|
||||
}
|
||||
return resolve(body);
|
||||
});
|
||||
};
|
|
@ -110,8 +110,9 @@ module.exports.handleCompleteRegistration = createProject => (dispatch => {
|
|||
// to be logged in before we try creating a project due to replication lag.
|
||||
window.location = '/';
|
||||
} else {
|
||||
dispatch(sessionActions.refreshSession());
|
||||
dispatch(module.exports.setRegistrationOpen(false));
|
||||
dispatch(sessionActions.refreshSessionWithRetry()).then(
|
||||
dispatch(module.exports.setRegistrationOpen(false))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ const keyMirror = require('keymirror');
|
|||
const defaults = require('lodash.defaults');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const sessionLib = require('../lib/session');
|
||||
const messageCountActions = require('./message-count.js');
|
||||
const permissionsActions = require('./permissions.js');
|
||||
|
||||
|
@ -103,3 +104,46 @@ module.exports.refreshSession = () => (dispatch => {
|
|||
return;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.refreshSessionWithRetry = () => (dispatch => {
|
||||
dispatch(module.exports.setStatus(module.exports.Status.FETCHING));
|
||||
return new Promise((resolve, reject) => (
|
||||
sessionLib.requestSessionWithRetry(1, resolve, reject)
|
||||
)).then(body => {
|
||||
if (typeof body === 'undefined') return dispatch(module.exports.setSessionError('No session content'));
|
||||
if (
|
||||
body.user &&
|
||||
body.user.banned &&
|
||||
banWhitelistPaths.indexOf(window.location.pathname) === -1
|
||||
) {
|
||||
window.location = '/accounts/banned-response/';
|
||||
return;
|
||||
} else if (
|
||||
body.flags &&
|
||||
body.flags.must_complete_registration &&
|
||||
window.location.pathname !== '/classes/complete_registration'
|
||||
) {
|
||||
window.location = '/classes/complete_registration';
|
||||
return;
|
||||
} else if (
|
||||
body.flags &&
|
||||
body.flags.must_reset_password &&
|
||||
!body.flags.must_complete_registration &&
|
||||
window.location.pathname !== '/classes/student_password_reset/'
|
||||
) {
|
||||
window.location = '/classes/student_password_reset/';
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setSession(body));
|
||||
dispatch(module.exports.setStatus(module.exports.Status.FETCHED));
|
||||
|
||||
// get the permissions from the updated session
|
||||
dispatch(permissionsActions.storePermissions(body.permissions));
|
||||
if (typeof body.user !== 'undefined') {
|
||||
dispatch(messageCountActions.getCount(body.user.username));
|
||||
}
|
||||
return;
|
||||
}, err => {
|
||||
dispatch(module.exports.setSessionError(err));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('JoinFlow', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
store = mockStore({sessionActions: {
|
||||
refreshSession: jest.fn()
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
}});
|
||||
});
|
||||
|
||||
|
@ -283,16 +283,31 @@ describe('JoinFlow', () => {
|
|||
expect(success).toEqual(false);
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed body with success', () => {
|
||||
test('handleRegistrationResponse calls refreshSessionWithRetry() when passed body with success', done => {
|
||||
const props = {
|
||||
refreshSession: jest.fn()
|
||||
refreshSessionWithRetry: () => (new Promise(() => { // eslint-disable-line no-undef
|
||||
done(); // ensures that joinFlowInstance.props.refreshSessionWithRetry() was called
|
||||
}))
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySuccess, {statusCode: 200});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse advances to next step when passed body with success', () => {
|
||||
const props = {
|
||||
refreshSessionWithRetry: () => (new Promise(resolve => { // eslint-disable-line no-undef
|
||||
resolve();
|
||||
}))
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySuccess, {statusCode: 200});
|
||||
process.nextTick(
|
||||
() => {
|
||||
expect(joinFlowInstance.state.registrationError).toEqual(null);
|
||||
expect(joinFlowInstance.props.refreshSession).toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.step).toEqual(1);
|
||||
expect(joinFlowInstance.state.waiting).toBeFalsy();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed body with preset server error', () => {
|
||||
|
@ -306,7 +321,7 @@ describe('JoinFlow', () => {
|
|||
|
||||
test('handleRegistrationResponse with failure response, with error fields missing', () => {
|
||||
const props = {
|
||||
refreshSession: jest.fn()
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
const responseErr = null;
|
||||
|
@ -320,7 +335,7 @@ describe('JoinFlow', () => {
|
|||
statusCode: 200
|
||||
};
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: null
|
||||
|
@ -339,7 +354,7 @@ describe('JoinFlow', () => {
|
|||
|
||||
test('handleRegistrationResponse with failure response, with no text explanation', () => {
|
||||
const props = {
|
||||
refreshSession: jest.fn()
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
const responseErr = null;
|
||||
|
@ -352,7 +367,7 @@ describe('JoinFlow', () => {
|
|||
statusCode: 200
|
||||
};
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: null
|
||||
|
@ -369,11 +384,11 @@ describe('JoinFlow', () => {
|
|||
|
||||
test('handleRegistrationResponse when passed status 400', () => {
|
||||
const props = {
|
||||
refreshSession: jest.fn()
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
joinFlowInstance.handleRegistrationResponse({}, responseBodyMultipleErrs, {statusCode: 400});
|
||||
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: true
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue