feat: implement safe leave, profile reset, and full game reset semantics
This commit is contained in:
+40
-1
@@ -118,6 +118,15 @@ static const char *phase_text(phase_t phase) {
|
||||
return phase <= PHASE_REMATCH_WAIT ? values[phase] : "lobby";
|
||||
}
|
||||
|
||||
static const char *recovery_reason_text(recovery_reason_t reason) {
|
||||
switch (reason) {
|
||||
case RECOVERY_REASON_SESSION_LEFT: return "session_left";
|
||||
case RECOVERY_REASON_PROFILE_RESET: return "profile_reset";
|
||||
case RECOVERY_REASON_GAME_RESET: return "game_reset";
|
||||
default: return "session_invalidated";
|
||||
}
|
||||
}
|
||||
|
||||
static void response_write(http_api_response_t *response, uint16_t status, const char *format, ...) {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
@@ -153,6 +162,16 @@ static void response_lifecycle_error(http_api_response_t *response, lifecycle_re
|
||||
}
|
||||
}
|
||||
|
||||
static void response_recovery(http_api_response_t *response, const game_lifecycle_t *lifecycle) {
|
||||
response_write(response, 200U, "{\"ok\":true,\"resetReason\":\"%s\",\"generation\":%" PRIu32 "}",
|
||||
recovery_reason_text(lifecycle->recovery_reason), lifecycle->recovery_generation);
|
||||
}
|
||||
|
||||
static void response_invalidated(http_api_response_t *response, const game_lifecycle_t *lifecycle) {
|
||||
response_write(response, 401U, "{\"ok\":false,\"code\":\"SESSION_INVALIDATED\",\"resetReason\":\"%s\",\"generation\":%" PRIu32 "}",
|
||||
recovery_reason_text(lifecycle->recovery_reason), lifecycle->recovery_generation);
|
||||
}
|
||||
|
||||
static bool expect_post_body(const http_api_request_t *request, size_t maximum, http_api_response_t *response,
|
||||
uint32_t version) {
|
||||
if (request->method != HTTP_API_POST || !request->content_type_json) {
|
||||
@@ -289,10 +308,14 @@ bool http_api_handle(http_api_t *api, const http_api_request_t *request, http_ap
|
||||
uint8_t token[kSessionTokenBytes];
|
||||
uint8_t session_index = 0;
|
||||
if (request->session_token != NULL && request->session_token[0] != '\0') {
|
||||
if (!parse_token(request->session_token, token) || !application_session_for_token(api->application, token, &session_index)) {
|
||||
if (!parse_token(request->session_token, token)) {
|
||||
response_error(response, 401U, "UNAUTHORIZED", "Сессия не найдена", version);
|
||||
return true;
|
||||
}
|
||||
if (!application_session_for_token(api->application, token, &session_index)) {
|
||||
response_invalidated(response, lifecycle);
|
||||
return true;
|
||||
}
|
||||
viewer = lifecycle->sessions.entries[session_index].role;
|
||||
}
|
||||
if (!state_presenter_write_lifecycle(lifecycle, viewer, response->body, sizeof(response->body), &response->body_length)) {
|
||||
@@ -374,6 +397,18 @@ bool http_api_handle(http_api_t *api, const http_api_request_t *request, http_ap
|
||||
return true;
|
||||
}
|
||||
if (!application_session_for_token(api->application, token, &command.session_index)) {
|
||||
if (request->route == HTTP_API_ROUTE_LEAVE || request->route == HTTP_API_ROUTE_PROFILE_RESET) {
|
||||
response_recovery(response, lifecycle);
|
||||
return true;
|
||||
}
|
||||
if (request->route == HTTP_API_ROUTE_RESET && lifecycle->recovery_reason == RECOVERY_REASON_GAME_RESET) {
|
||||
response_recovery(response, lifecycle);
|
||||
return true;
|
||||
}
|
||||
if (request->route == HTTP_API_ROUTE_RESET) {
|
||||
response_error(response, 403U, "FORBIDDEN_ROLE", "Роль не может выполнить действие", version);
|
||||
return true;
|
||||
}
|
||||
response_error(response, 401U, "UNAUTHORIZED", "Сессия не найдена", version);
|
||||
return true;
|
||||
}
|
||||
@@ -383,11 +418,15 @@ bool http_api_handle(http_api_t *api, const http_api_request_t *request, http_ap
|
||||
case HTTP_API_ROUTE_SHOT: command.type = COMMAND_SHOT; break;
|
||||
case HTTP_API_ROUTE_REMATCH: command.type = COMMAND_REMATCH; break;
|
||||
case HTTP_API_ROUTE_ABORT: command.type = COMMAND_ABORT; break;
|
||||
case HTTP_API_ROUTE_LEAVE: command.type = COMMAND_LEAVE; break;
|
||||
case HTTP_API_ROUTE_PROFILE_RESET: command.type = COMMAND_PROFILE_RESET; break;
|
||||
case HTTP_API_ROUTE_RESET: command.type = COMMAND_RESET; break;
|
||||
default: response_error(response, 404U, "MALFORMED_JSON", "Маршрут не найден", version); return true;
|
||||
}
|
||||
command.version = version;
|
||||
const lifecycle_result_t result = application_submit(api->application, &command);
|
||||
if (result != LIFECYCLE_RESULT_OK) response_lifecycle_error(response, result, lifecycle->game.state.version);
|
||||
else if (request->route == HTTP_API_ROUTE_LEAVE || request->route == HTTP_API_ROUTE_PROFILE_RESET || request->route == HTTP_API_ROUTE_RESET) response_recovery(response, lifecycle);
|
||||
else response_write(response, 200U, "{\"ok\":true,\"version\":%" PRIu32 ",\"gameId\":%" PRIu32 "}",
|
||||
lifecycle->game.state.version, lifecycle->game.state.game_id);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user