ProductionGame/frontend/js/core/sync.js

117 lines
3.7 KiB
JavaScript

// Production - The Idle Game: Backend Synchronization & Authentication
const TOKEN_KEY = 'production_auth_token';
const USERNAME_KEY = 'production_username';
export class SyncManager {
constructor(stateManager) {
this.sm = stateManager;
this.token = localStorage.getItem(TOKEN_KEY) || null;
this.username = localStorage.getItem(USERNAME_KEY) || null;
this.isSyncing = false;
this.pendingActions = [];
this.debounceTimer = null;
}
isLoggedIn() {
return !!this.token;
}
getUsername() {
return this.username || 'Executive Guest';
}
setAuth(token, username) {
this.token = token;
this.username = username;
localStorage.setItem(TOKEN_KEY, token);
localStorage.setItem(USERNAME_KEY, username);
}
logout() {
this.token = null;
this.username = null;
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USERNAME_KEY);
}
queueAction(action) {
this.pendingActions.push(action);
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.flush(), 1000);
}
async flush() {
if (!this.token || this.isSyncing || this.pendingActions.length === 0) return;
this.isSyncing = true;
const actionsToSend = [...this.pendingActions];
this.pendingActions = [];
try {
const res = await fetch('/api/game/tick', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({
seq: Date.now(),
actions: actionsToSend,
client_tick: Date.now() / 1000
})
});
if (!res.ok) {
// If unauthorized, clear token
if (res.status === 401) this.logout();
// Return unsent actions to queue
this.pendingActions.unshift(...actionsToSend);
}
} catch (err) {
console.warn('Sync failed (will retry):', err);
this.pendingActions.unshift(...actionsToSend);
} finally {
this.isSyncing = false;
}
}
async fetchCloudState() {
if (!this.token) return null;
try {
const res = await fetch('/api/game/state', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
if (res.ok) {
const data = await res.json();
return data.save_data || null;
}
} catch (err) {
console.error('Error fetching cloud state:', err);
}
return null;
}
async login(username, password) {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Login failed');
this.setAuth(data.token, data.username);
return data;
}
async register(username, password, website_hp = '') {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, website_hp })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Registration failed');
this.setAuth(data.token, data.username);
return data;
}
}