src/actions/authentication.jsimport { getServiceURL, handleHTTPErrors } from '../utilities/rest';export const USER_LOGIN_BEGIN = 'USER_LOGIN_BEGIN'; export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';
Три строковые константы для использования редьюсером (reducers/authentication.js). switch(type) в редьюсере сопоставляет эти строки.
USER_LOGOUT был здесь раньше — удалён в коммите 7c60c2fbb (июль 2019).
export const userLoginBegin = () => ({
type: USER_LOGIN_BEGIN,
});
export const userLoginSuccess = (user, version, buildTimestamp, alertMessage) => ({
type: USER_LOGIN_SUCCESS,
payload: { user, version, buildTimestamp, alertMessage },
});
export const userLoginFailure = (error) => ({
type: USER_LOGIN_FAILURE,
payload: { error },
});Action creators — чистые функции, возвращающие простые объекты. Синхронные «сообщения» для редьюсера.
| Creator | Сигнатура | Что делает редьюсер |
|---|---|---|
userLoginBegin() | () → { type: BEGIN } | Сброс: { loading: true, error: null, user: null } |
userLoginSuccess(u, v, b, a) | (obj, str, str, str?) → { type, payload } | Записывает все 4 поля в state |
userLoginFailure(e) | (str) → { type, payload: { error } } | Сохраняет ошибку, очищает user |
const catchError = (dispatch) => (error) => dispatch(userLoginFailure(error.message));
Каррированная функция. Первый вызов захватывает dispatch, второй — error. Используется в .catch() обоих thunk'ов.
loadUserStatus() вызывает catchError(dispatch)({ message: undefined }) — передаёт { message: undefined } потому что сам обрабатывает ошибку редиректом.
export const loadUserStatus = () => (dispatch) => {
dispatch(userLoginBegin());
return fetch(
getServiceURL('userStatus'),
{ method: 'GET', credentials: 'include' },
)
.then(handleHTTPErrors)
.then((response) => response.json())
.then(({ userData, systemData, alertMessage }) => {
dispatch(userLoginSuccess(
userData,
systemData.version,
systemData.buildTimestamp,
alertMessage,
));
})
.catch(() => {
const { pathname, search } = window.location;
const href = pathname + search;
if (!pathname.startsWith('/react/public/login')
&& !pathname.startsWith('/react/public/datatransfer/')) {
window.location.href =
`/react/public/login?url=${encodeURIComponent(href)}`;
}
catchError(dispatch)({ message: undefined });
});
};Назначение: «Кто я?» — вызывается при старте приложения и после логина.
(a) Диспатчит USER_LOGIN_BEGIN.
(b) GET /rs/userStatus — отправляет сессионную куку. Попадает в UserStatusRest.kt:111.
(c–e) Успех: парсит JSON, диспатчит SUCCESS.
(f) Ошибка: 401 → редирект на /react/public/login?url=.... catchError(dispatch)({ message: undefined }) — очищает ошибку.
export const login = (username, password, keepSignedIn) => (dispatch) => {
dispatch(userLoginBegin());
return fetch(
getServiceURL('/rsPublic/login'),
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username, password,
stayLoggedIn: keepSignedIn,
}),
credentials: 'include',
},
)
.then(handleHTTPErrors)
.then(() => loadUserStatus()(dispatch))
.catch(catchError(dispatch));
};Назначение: отправить учётные данные. При успехе — узнать кто залогинился.
(a) Диспатчит BEGIN.
(b) POST /rsPublic/login. Сервер ставит JSESSIONID. Попадает в LoginPageRest.kt:93.
(c) stayLoggedIn: keepSignedIn — флажок «оставаться в системе». Сервер ставит куку stayLoggedIn на 30 дней (CookieService.kt:201).
(d–e) Успех → loadUserStatus()(dispatch). Именно поэтому тест ожидает 3 действия: BEGIN (login) + BEGIN (loadUserStatus) + SUCCESS.
(f) Ошибка → catchError → FAILURE.
ЗАПУСК:
ProjectForge.jsx → dispatch(loadUserStatus())
└─ GET /rs/userStatus
├─ 200 → SUCCESS(userData, systemData, alertMessage)
└─ 401 → редирект на /react/public/login
ЛОГИН:
login(username, password, keepSignedIn)
├─ dispatch(BEGIN)
├─ POST /rsPublic/login
├─ сервер ставит JSESSIONID (+ stayLoggedIn если keepSignedIn)
├─ 200 → loadUserStatus()(dispatch) → BEGIN → SUCCESS
└─ 401/ошибка → catchError → FAILURE
Связанные доки: комментарий к authentication.test.js | комментарий к reducers/authentication.js
Две утилиты из rest.js:
getServiceURL(path)— строит полный URL: добавляетhttp://localhost:8080(dev) или""(prod).'/rsPublic/login'→ в devhttp://localhost:8080/rsPublic/login.handleHTTPErrors(response)— бросаетError('Fetch failed: Error {status}')если!response.ok.