135 lines
4.2 KiB
JavaScript
135 lines
4.2 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 saveCloudState(saveData) {
|
|
if (!this.token) return false;
|
|
try {
|
|
const res = await fetch('/api/game/save', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${this.token}`
|
|
},
|
|
body: JSON.stringify({ save_data: saveData })
|
|
});
|
|
return res.ok;
|
|
} catch (err) {
|
|
console.error('Error saving cloud state:', err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|