Files
BreakEscape/public/break_escape/js/api-client.js
Z. Cliffe Schreuders a945859730 Implement comprehensive server-side validation and data filtering for client actions
Server-side changes:
- Game model: Initialize starting items in player inventory from scenario
- Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents
- Game model: Fix filtered_room_data to preserve lockType while removing requires
- GamesController: Add scenario_map endpoint for minimal layout metadata
- GamesController: Update room endpoint with access control and NPC encounter tracking
- GamesController: Add container endpoint for lazy-loading locked container contents
- GamesController: Update inventory endpoint with comprehensive validation
  - Validates item exists in scenario
  - Checks item is takeable
  - Verifies container is unlocked if item is in container
  - Verifies room is unlocked if room is locked
  - Checks NPC is encountered if item held by NPC
- GamesController: Update unlock endpoint with transaction safety
- GamesController: Update sync_state to verify room accessibility
- Routes: Add scenario_map and container routes

Client-side changes:
- inventory.js: Make addToInventory async and add server validation before local updates
- container-minigame.js: Add lazy-loading of container contents from server
- game.js: Update to use scenario_map endpoint for reduced initial payload
- api-client.js: Add getScenarioMap method alongside getScenario

Security improvements:
- Prevents client-side cheating by validating all actions server-side
- Hides solution fields (requires) from client responses
- Hides locked container contents until unlocked
- Enforces room and container access controls
- Tracks NPC encounters automatically
- All validation failures return clear error messages

Implements plans from:
- planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md
- planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00

115 lines
2.5 KiB
JavaScript

import { API_BASE, CSRF_TOKEN } from './config.js';
/**
* API Client for BreakEscape server communication
*/
export class ApiClient {
/**
* GET request
*/
static async get(endpoint) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: 'GET',
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status} ${response.statusText}`);
}
return response.json();
}
/**
* POST request
*/
static async post(endpoint, data = {}) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': CSRF_TOKEN
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
throw new Error(error.error || `API Error: ${response.status}`);
}
return response.json();
}
/**
* PUT request
*/
static async put(endpoint, data = {}) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: 'PUT',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': CSRF_TOKEN
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Get scenario JSON (full scenario data)
static async getScenario() {
return this.get('/scenario');
}
// Get scenario map (minimal layout metadata for navigation)
static async getScenarioMap() {
return this.get('/scenario_map');
}
// Get NPC script
static async getNPCScript(npcId) {
return this.get(`/ink?npc=${npcId}`);
}
// Validate unlock attempt
static async unlock(targetType, targetId, attempt, method) {
return this.post('/unlock', {
targetType,
targetId,
attempt,
method
});
}
// Update inventory
static async updateInventory(action, item) {
return this.post('/inventory', {
action,
item
});
}
// Sync player state
static async syncState(currentRoom, globalVariables) {
return this.put('/sync_state', {
currentRoom,
globalVariables
});
}
}
// Export for global access
window.ApiClient = ApiClient;