fix: sync avatar details and Srtool session

This commit is contained in:
CAtlas0784 committed 2026-10-18 20:31:15 +07:00
1 parent dc097d8a0a
commit fa31704a56
2 files changed
+38 -9

No files matched your search

+4
View File
@@ -111,3 +111,7 @@ GaC-Srtool สื่อสารกับเซิร์ฟเวอร์ GaC-h
| `/api/avatar/rank` | POST | เปลี่ยนระดับ Eidolon ของตัวละคร |
| `/api/avatar/max_all` | POST | ปรับตัวละครทุกตัวเป็น Max E6 และสกิลเต็ม |
| `/api/command` | POST | ส่งคำสั่ง GM ไปยังเซิร์ฟเวอร์ |
### บันทึกตัวละครและ Traces
การบันทึกจากหน้ารายละเอียดส่งข้อมูลตัวละครทั้งชุดผ่าน `/api/freesr` ในคำขอเดียว รวมถึง `data.skills` และ `data.skills_by_anchor_type` สำหรับ Traces เพื่อไม่ให้ server sync ข้อมูลระดับ/Eidolon ระหว่างทางก่อนข้อมูล Trace จะถูกบันทึก `saveAvatarDetails(closeModal = true)` ยังรองรับการเรียกจากปุ่ม build ที่ไม่ต้องปิดหน้าต่าง ถ้า server ตอบผิดพลาดจะแสดงข้อความบันทึกไม่สำเร็จแทนการแจ้งว่าสำเร็จ
+34 -9
View File
@@ -381,7 +381,8 @@ async function apiCall(endpoint, method = 'GET', data = null) {
const options = {
method,
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'X-GaC-Srtool': '1'
}
};
@@ -409,8 +410,34 @@ async function apiCall(endpoint, method = 'GET', data = null) {
}
}
// Explicit, expiring custom-battle connection. The backend returns its TTL.
let toolSessionTimer = null;
let toolSessionConnected = false;
let toolSessionGeneration = 0;
async function syncToolSession(connected) {
const generation = ++toolSessionGeneration;
if (toolSessionTimer) { clearTimeout(toolSessionTimer); toolSessionTimer = null; }
toolSessionConnected = connected;
try {
const result = await apiCall('/api/tool-session', 'POST', { connected, client: 'GaC-Srtool' });
if (generation === toolSessionGeneration && connected && toolSessionConnected && Number(result.lease_seconds) > 0) {
toolSessionTimer = setTimeout(() => syncToolSession(true), Number(result.lease_seconds) * 1000 / 3);
}
} catch (error) {
if (generation === toolSessionGeneration) toolSessionConnected = false;
// Older backends remain usable, but cannot enable the custom encounter lease.
console.warn('Tool session unavailable:', error.message);
}
}
window.addEventListener('pagehide', () => {
if (!toolSessionConnected) return;
const base = state.apiUrl.replace(/\/+$/, '');
navigator.sendBeacon(`${base}/api/tool-session`, JSON.stringify({ connected: false, client: 'GaC-Srtool' }));
});
// Update connection status UI
function setConnectionStatus(connected, extraText = '') {
if (connected !== toolSessionConnected) syncToolSession(connected);
state.isConnected = connected;
if (connected) {
el.statusDot.classList.add('connected');
@@ -4600,7 +4627,7 @@ async function maxAllBuildForCurrentAvatar() {
}
// Save avatar details from modal
async function saveAvatarDetails() {
async function saveAvatarDetails(closeModal = true) {
if (!state.selectedAvatarDetail) return;
const aid = state.selectedAvatarDetail.avatar_id;
const level = parseInt(el.modalCharLevel.value, 10) || 80;
@@ -4735,15 +4762,13 @@ async function saveAvatarDetails() {
if (state.isConnected) {
try {
if (state.serverMode === 'gac') {
await apiCall('/api/freesr/avatars', 'POST', { avatar_id: aid, level, promotion, rank });
await saveFreesrChangesToServer();
} else {
await saveFreesrChangesToServer();
}
// Save the complete avatar record once. The full payload includes skills
// and Trace values; a preceding rank-only request can trigger live sync
// with an incomplete intermediate state.
await saveFreesrChangesToServer();
showToast(t('toastCharSaved', { name: getAvatarInfo(aid).name }), 'success');
} catch (err) {
showToast(t('toastCharSaved', { name: getAvatarInfo(aid).name }), 'info');
showToast(`บันทึกไม่สำเร็จ: ${err.message}`, 'error');
}
} else {
showToast(t('toastCharSaved', { name: getAvatarInfo(aid).name }), 'success');