mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
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
115 lines
2.5 KiB
JavaScript
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;
|