FireflyGO api support
Deploy GaC-Srtool to GitHub Pages / deploy (push) Canceled after 0s

This commit is contained in:
CAtlas0784 committed 2026-11-04 17:14:50 +07:00
1 parent 8eb519023a
commit 1593581729
18 files changed
+2757 -98

No files matched your search

+553
View File
@@ -0,0 +1,553 @@
# FireflyGO Endgame Room Picker — สถานะงานและคู่มือเทคนิค
> อัปเดต: 2026-11-04
> ขอบเขต: งานเพิ่มโหมดเชื่อมต่อ FireflyGO (Firefly_Srtools) ใน GaCy-Srtool และหน้าตั้งค่าห้อง Endgame ให้เหมือนต้นฉบับ `https://srtools.punklorde.org`
---
## 1. สรุปสั้น
### โจทย์
เว็บ GaCy-Srtool เดิมออกแบบมาสำหรับเซิร์ฟเวอร์ **GaCy GO** ซึ่งมี REST API ครบ (`/api/status`, `/api/freesr`, `/api/persistent`, `/api/monster-settings`, ...) แต่ผู้ใช้ต้องการเชื่อมกับ **FireflyGO (Firefly_Srtools)** ซึ่งเป็นแบบ **push-only** คือมีแค่ `POST <base>/sync`
ปัญหาที่ตามมา:
1. ต้องเพิ่มโหมด FireflyGO โดยไม่ทำให้โหมด GaCy เดิมพัง
2. ต้องซ่อน UI ที่ต้องใช้ endpoint ที่ FireflyGO ไม่มี (save manager, inventory, exrank, damage analyst, ...)
3. ต้องแก้ `extra_data` ของ FireflyGO ได้ (multi-path, setting, challenge, theory_craft, lua)
4. ต้องนำเข้าข้อมูลจาก UID เกมจริง (Enka) ได้ทุกโหมด
5. **ต้องมีหน้า Monster Setting / เลือกห้อง Endgame แบบต้นฉบับ Firefly** (MoC, PF, AS, Anomaly Arbitration, Custom Enemy)
### สถานะปัจจุบัน (สรุป)
| ส่วน | สถานะ |
| :--- | :--- |
| โหมด FireflyGO + `POST /sync` | ✅ เสร็จและ commit แล้ว (`8eb5190`) |
| `extra_data` form | ✅ เสร็จ |
| Enka UID → FreeSR import | ✅ เสร็จ |
| ซ่อน/แสดง UI ตามโหมด | ✅ เสร็จ |
| แคตตาล็อกห้อง Endgame (moc/pf/as/peak) | ✅ สร้างเสร็จ (`assets/firefly-endgame.json`) |
| ตัวช่วยคำนวณ `battle_config` (`js/endgame.js`) | ✅ เสร็จ |
| UI หน้า Monster Setting โหมด Firefly | ✅ เขียนเสร็จ (เรียง season/floor ใหม่สุดก่อนแล้ว) |
| ปรับปรุง Auto-init FreeSR เมื่อ Apply ห้อง | ✅ เสร็จ (ไม่ติดบล็อก `ffEndgameNoData`) |
| ดักจับ `player data is not loaded yet` & Mixed Content | ✅ เสร็จ (มีข้อความแจ้งเตือนชัดเจน) |
| Unit tests | ✅ ผ่าน **44/44** |
| ทดสอบมือถือ 360–768px ของหน้าใหม่ | ✅ ผ่าน (responsive styles พร้อม) |
| ทดสอบ `POST /sync` กับ FireflyGO จริง | ⏳ รอเปิดเซิร์ฟเวอร์และตัวละครล็อกอินในเกม |
| Commit งานรอบนี้ | ⏳ พร้อม commit |
---
## 2. สถาปัตยกรรมโหมดเชื่อมต่อ
ระบบมี 3 โหมด เก็บใน `localStorage['gac_srtool_mode']` และนิยามความสามารถไว้ที่ `js/modes.js`
| ความสามารถ | GaCy GO (`gac`) | FireflyGO (`firefly`) | RobinSR (`robin`) |
| :--- | :---: | :---: | :---: |
| `saveManager` | ✅ | — | — |
| `exranks` | ✅ | — | — |
| `inventory` | ✅ | — | — |
| `monsterSettings` (อ่าน catalog จาก Client) | ✅ | — | — |
| `fireflyEndgame` (picker จากแคตตาล็อกในเครื่อง) | — | ✅ | — |
| `damageAnalyst` | ✅ | — | — |
| `sceneCard` / `gmConsole` / `battleSummary` | ✅ | — | — |
| `toolSession` | ✅ | — | — |
| `extraData` | — | ✅ | — |
| `ownedAvatarsOnly` | — | ✅ | — |
| `jsonModes` | `freesr`, `persistent` | `freesr` | `freesr` |
ไฟล์ที่เกี่ยวข้อง:
- `js/modes.js` — capability matrix, `normalizeMode`, `modeSupports`
- `js/app.js` → `applyModeDisplay()` — ซ่อน/แสดง tab และ card ตาม capability
- `js/app.js` → `setupServerModes()` — ฟัง event เปลี่ยนโหมด (ผูกครั้งเดียว)
### กฎสำคัญ
- **FireflyGO เป็น push-only**: การ "เชื่อมต่อ" คือ `POST <base>/sync` ด้วย `{ username:'', password:'' }` แล้วอ่าน `{ status, message, extra_data, warnings, stats }`
- การซิงค์คือ `POST <base>/sync` ด้วย `{ username, password, data: FreeSRJson, extra_data }`
- **ห้ามใส่ custom header** เพราะ FireflyGO ไม่รองรับ CORS preflight
- สถานะการเชื่อมต่อของ FireflyGO เก็บใน `state.fireflyConnected` (ไม่ใช้ `state.isConnected` ร่วมกับ GaCy)
- `refreshStatus()` ในโหมด Firefly เป็น local-only (ไม่อ่าน `/api/status`)
---
## 3. FireflyGO Endgame Room Picker
### 3.1 ภาพรวมการทำงาน
```mermaid
sequenceDiagram
autonumber
actor User as ผู้ใช้
participant UI as app.js (Monster Setting)
participant Cat as assets/firefly-endgame.json
participant Save as state.fullFreesrData
participant Srv as FireflyGO /sync
User->>UI: เปิดแท็บ Monster Setting (โหมด FireflyGO)
UI->>Cat: fetch แคตตาล็อก (ครั้งเดียว)
Cat-->>UI: modes.{moc,pf,as,peak} + monsters
UI->>Save: อ่าน battle_config เดิม
UI->>UI: syncFireflyEndgameSelectionFromSave()
UI-->>User: แสดง season / floor / side / stage / preview
User->>UI: เลือกห้อง + กด "ใช้กับ battle_config"
UI->>UI: buildFireflyBattleConfig()
UI->>Save: เขียน battle_config
opt Anomaly Arbitration
UI->>UI: buildFireflyChallenge() → extra_data.challenge
end
UI->>UI: saveFreesrCache()
User->>UI: กด "ใช้และซิงค์"
UI->>Srv: POST /sync { data, extra_data }
Srv-->>UI: { status, warnings }
```
### 3.2 แหล่งข้อมูลแคตตาล็อก
ต้นฉบับ Firefly SrTools เสิร์ฟตารางดิบของ Client ที่:
```
https://srtools.punklorde.org/api/data/moc
https://srtools.punklorde.org/api/data/pf
https://srtools.punklorde.org/api/data/as
https://srtools.punklorde.org/api/data/peak
https://srtools.punklorde.org/api/data/monster
```
ไฟล์ดิบมีขนาด 2–14 MB จึงทำสคริปต์ย่อข้อมูล:
```bash
node scripts/build-firefly-endgame-data.cjs [cacheDir]
```
- ถ้ามี `cacheDir` และมีไฟล์อยู่แล้วจะใช้แคช (ไม่ยิงเน็ตซ้ำ)
- ผลลัพธ์: `assets/firefly-endgame.json` (~604 KB)
- ข้อมูล ณ วันที่สร้าง:
| โหมด | จำนวนกลุ่ม (season/round) |
| :--- | ---: |
| `moc` (Memory of Chaos) | 58 |
| `pf` (Pure Fiction) | 28 |
| `as` (Apocalyptic Shadow) | 23 |
| `peak` (Anomaly Arbitration) | 11 |
| มอนสเตอร์ (unique) | 355 |
โครงสร้างไฟล์:
```jsonc
{
"version": "2026-11-03",
"source": "https://srtools.punklorde.org/api/data",
"modes": {
"moc": { "groups": [ { "id": 1037, "name": {"en":"..."}, "begin": "...", "end": "...",
"levels": [ { "id": 5612, "floor": 12, "turnLimit": 30,
"buffs": [ { "id": 1001, "name": {"en":"..."} } ],
"sides": [ { "side": "firstNode", "stageId": 30127111, "level": 92,
"name": {"en":"..."}, "waves": [[4033010, 4033020],[4044010]],
"invasionBuffId": 3034002 } ] } ] } ] },
"pf": { "groups": [] },
"as": { "groups": [] },
"peak": { "groups": [ { "id": 9, "name": {"en":"..."},
"stages": [ { "kind": "pre|boss|hard", "stageId": 4001, "level": 80,
"name": {"en":"..."}, "waves": [[...]], "buffs": [...], "turnLimit": 30 } ] } ] }
},
"monsters": {
"4033010": { "name": {"en":"...","th":"..."}, "rank": 1, "stance": 1,
"weak": ["Fire"], "icon": "https://cdn.punklorde.org/asbres/spriteoutput/mostericon/monster_4033010.webp" }
}
}
```
> หมายเหตุ: path ไอคอนของข้อมูลต้นทางสะกดว่า `mostericon` (ตก `n`) จึงต้องใช้ตามนั้น
### 3.3 กติกาการแปลงข้อมูลจากต้นฉบับ
| โหมด | แหล่งห้อง | กติกา |
| :--- | :--- | :--- |
| MoC | `Level[].EventList1[0]` (ครึ่งแรก), `EventList2[0]` (ครึ่งหลัง), `Tierce.EventList[0]` (โหนดที่สาม) | `stage_id` = `stage.ID`, `cycle_count` = `Level.TurnLimit`, blessings = `Level.MazeBuff` + `Invasion.MazeBuff.ID` |
| PF | เหมือน MoC | เหมือน MoC |
| AS | เหมือน MoC | เหมือน MoC |
| Anomaly Arbitration (`peak`) | กลุ่ม `peak[event_id]` → เลือก stage จาก `[...PreLevel, BossLevel]` ตาม index | ถ้า `boss_mode === 'Hard'` ใช้ `BossConfig`; `stage_id = EventList[0].ID`; ต้องส่ง `extra_data.challenge.challenge_peak_group_id` = group id |
| Custom Enemy (`custom`/`CE`) | ผู้ใช้กรอกเอง | `stage_id = 0`, ใช้ `monsters` ที่กรอก |
### 3.4 Schema ที่ FireflyGO ตรวจ (zod)
จากการแกะ bundle ต้นฉบับ:
```ts
battle_config = {
battle_type: string, // MOC | PF | AS | CE | PEAK | DEFAULT
blessings: { id: number, level: number, dynamic_key?: { key: any, value: number } }[],
custom_stats: any[],
cycle_count: number,
stage_id: number,
path_resonance_id?: number, // default 0
monsters: { monster_id: number, level: number, amount?: number }[][]
}
```
จุดที่พลาดง่าย:
- `blessings` **ต้องเป็น object** `{id, level}` ไม่ใช่ array ของ id
- `monsters` เป็น **array ของ wave** และแต่ละตัวเป็น object
- `amount` ไม่บังคับ แต่เราเติม `1` ให้เสมอเพื่อความชัดเจน
`js/firefly.js → normalizeBattleConfig()` จึงแปลงค่าดิบให้เป็น shape นี้ก่อนส่งทุกครั้ง (รองรับข้อมูลเก่าที่เป็น id ล้วนด้วย)
### 3.5 ตัวช่วยคำนวณ (`js/endgame.js`)
ฟังก์ชันทั้งหมดเป็น pure function (ทดสอบด้วย `node:test` ได้):
| ฟังก์ชัน | หน้าที่ |
| :--- | :--- |
| `battleTypeForMode(mode)` | แปลง `moc/pf/as/peak/custom` → `MOC/PF/AS/PEAK/CE` |
| `groupList(catalog, mode)` | คืนกลุ่มของโหมด |
| `findGroup` / `findLevel` | ค้นหากลุ่ม/ชั้น |
| `defaultEndgameSelection(catalog, mode)` | ค่าเริ่มต้น: ซีซันใหม่สุด + ชั้นสูงสุด + ฝั่งแรก; peak = stage บอส |
| `resolveEndgameSelection(catalog, selection)` | คืน `{group, level, sides, stage}` หรือ `null` |
| `findSelectionByStageId(catalog, mode, stageId, groupId)` | เปิด picker ที่ห้องเดียวกับ `battle_config` เดิม |
| `buildFireflyBattleConfig(catalog, selection)` | สร้าง `battle_config` พร้อมส่ง |
| `buildFireflyChallenge(selection, current)` | อัปเดต `extra_data.challenge` (เฉพาะ peak) |
| `summarizeEndgameSelection` | สรุปจำนวน wave/มอน/บัฟ/รอบ ให้ UI |
| `monsterInfo(catalog, id)` | ข้อมูลมอน (ชื่อ/ไอคอน) |
### 3.6 State ของ UI
เพิ่มใน `state.fireflyEndgame` (`js/app.js`):
```js
{
activeMode: 'moc', // moc | pf | as | peak | custom
catalog: null, // แคตตาล็อกที่โหลดแล้ว
catalogState: 'idle', // idle | loading | ready | error
catalogError: '',
catalogPromise: null, // กัน fetch ซ้ำ
selections: {}, // แยก selection ตามโหมด
statusKey: '', statusVars: null, statusState: 'info'
}
```
`selections` เก็บ object ที่ผูกกับ UI โดยตรง เช่น
```js
// moc/pf/as
{ mode:'moc', groupId:1037, levelId:5612, side:'firstNode', useCycle:true, useBuff:true }
// peak
{ mode:'peak', groupId:9, stageIndex:1, useCycle:true, useBuff:true }
// custom
{ mode:'custom', customWaves:[[{monster_id:'4033010', level:'90', amount:'1'}]], customCycle:0 }
```
### 3.7 UI ที่เพิ่มใน `index.html`
`#tab-monster-settings` ถูกแบ่งเป็น 2 ส่วน:
```html
<section id="tab-monster-settings" class="tab-content">
<div id="monster-settings-gac"> ... ของเดิมทั้งหมด ... </div>
<div id="monster-settings-firefly" hidden>
<div class="card monster-settings-intro"> ... คำอธิบาย ... </div>
<div class="monster-settings-tabs" id="ff-endgame-tabs">
<button data-ff-endgame-mode="moc">Memory of Chaos</button>
<button data-ff-endgame-mode="pf">Pure Fiction</button>
<button data-ff-endgame-mode="as">Apocalyptic Shadow</button>
<button data-ff-endgame-mode="peak">Anomaly Arbitration</button>
<button data-ff-endgame-mode="custom">Custom Enemy</button>
</div>
<div id="ff-endgame-status" class="monster-settings-status"></div>
<div id="ff-endgame-view" class="monster-settings-view"></div>
<div class="ff-endgame-actions">
<button id="ff-endgame-reset">รีเซ็ตค่าเริ่มต้น</button>
<button id="ff-endgame-apply">ใช้กับ battle_config</button>
<button id="ff-endgame-apply-sync">ใช้และซิงค์</button>
</div>
</div>
</section>
```
การแสดงผล:
- `applyModeDisplay()` ตั้ง `hidden` ให้ `#monster-settings-gac` / `#monster-settings-firefly` ตามโหมด
- แท็บ Monster Setting แสดงเมื่อ `caps.monsterSettings || caps.fireflyEndgame`
- ถ้าแท็บถูกซ่อนอยู่และโหมดเปลี่ยน ระบบจะพากลับไป Dashboard อัตโนมัติ
### 3.8 ฟังก์ชันใน `js/app.js`
| ฟังก์ชัน | หน้าที่ |
| :--- | :--- |
| `loadFireflyEndgame()` | โหลด snapshot (memory → IndexedDB → localStorage) แล้วโหลดแคตตาล็อก |
| `ensureFireflyEndgameCatalog()` | `fetch` แคตตาล็อกครั้งเดียว + seed ค่าเริ่มต้นทุกโหมด |
| `syncFireflyEndgameSelectionFromSave()` | อ่าน `battle_config` เดิมมาเลือกห้องให้ตรง |
| `renderFireflyEndgame()` | entry point ของการวาด UI |
| `renderFireflyRoomEditor()` | วาด season/floor/side หรือ stage cards + preview |
| `renderFireflyCustomEditor()` | วาด editor ของ Custom Enemy |
| `renderFireflyEndgamePreview()` | แสดง `battle_config` ที่จะส่งจริง |
| `setupFireflyEndgameEvents()` | ผูก event ทั้งหมด (ผูกครั้งเดียวตอน init) |
| `applyFireflyEndgame({sync})` | เขียน `battle_config` (+`challenge`) แล้วบันทึก/ซิงค์ |
| `resetFireflyEndgame()` | รีเซ็ตโหมดปัจจุบันเป็นค่าเริ่มต้น |
| `updateFireflyEndgameControls()` | เปิด/ปิดปุ่มตามความพร้อม |
| `setFireflyEndgameStatus()` | ข้อความสถานะ (i18n) |
จุดเชื่อมกับระบบเดิม:
- `renderMonsterSettings()` → ถ้าโหมด firefly เรียก `renderFireflyEndgame()` แล้ว return
- `loadMonsterSettings()` → ถ้าโหมด firefly เรียก `loadFireflyEndgame()` แล้ว return
- `setConnectionStatus()` → โหลด Monster Setting ใหม่เฉพาะโหมด `gac`
- `applyTranslations()` → เรียก `renderMonsterSettings()` ใหม่เมื่อสลับภาษา
- `updateDocumentTitle()` → ใช้ `ffEndgameTitle` เมื่ออยู่โหมด firefly
### 3.9 Apply ทำอะไรบ้าง
`applyFireflyEndgame({ sync })`:
1. `buildFireflyBattleConfig()` จาก selection ปัจจุบัน
2. ถ้าไม่มีข้อมูลหรือเลือกไม่ครบ → แจ้ง error และไม่เขียนอะไร
3. เขียน `state.fullFreesrData.battle_config = config`
4. ถ้าโหมดเป็น `peak`:
- `extra.challenge = buildFireflyChallenge(selection, extra.challenge)`
- เก็บ `state.fireflyExtraData` + `persistFireflyExtra()`
- รีเฟรชการ์ด extra_data
5. ถ้า `sync = false` → `saveFreesrCache()` (IndexedDB + localStorage)
6. ถ้า `sync = true` → `saveFreesrChangesToServer()` ซึ่งในโหมด firefly จะไปเรียก `pushFireflySync()` → `POST /sync`
7. อัปเดตสถานะ + toast
> `pushFireflySync()` จะบังคับ `extra_data.theory_craft.mode` จาก `state.creativeMode` เสมอ เพื่อให้ Creative Mode กับ theory craft เป็นสวิตช์เดียวกัน
### 3.10 i18n
- เพิ่มคีย์ `ffEndgame*` **46 คีย์** ใน `MODAL_AND_TOOL_TRANSLATIONS` ทั้ง `th` และ `en`
- ภาษาอื่น fallback เป็นอังกฤษผ่าน `t()` ตามกติกาเดิม
- ข้อความที่วาดจาก JS ทุกจุดใช้ `t(...)` ไม่มีข้อความ hardcode
- `applyTranslations()` รีเรนเดอร์หน้า Monster Setting ทำให้ชื่อห้อง/มอนเปลี่ยนตามภาษา
ตัวอย่างคีย์:
`ffEndgameTitle`, `ffEndgameDescription`, `ffEndgameRule`, `ffEndgameModePeak`, `ffEndgameSeason`, `ffEndgameFloor`, `ffEndgameSideFirst/Second/Third`, `ffEndgameStageKindPre/Boss/Hard`, `ffEndgameUseCycle`, `ffEndgameUseBuff`, `ffEndgameApply`, `ffEndgameApplySync`, `ffEndgameReset`, `ffEndgameSaved`, `ffEndgameSynced`, `ffEndgameSyncError`, `ffEndgameMonsterId/Level/Amount`, ...
### 3.11 CSS
เพิ่มบล็อก `.ff-endgame-*` ใน `css/styles.css`:
- `.ff-endgame-picker` — grid 2 คอลัมน์อัตโนมัติ
- `.ff-endgame-sides` / `.ff-endgame-side` — ปุ่มเลือกฝั่ง
- `.ff-endgame-toggles` — สวิตช์ TurnLimit / Buff
- `.ff-endgame-preview` — preview การ์ด
- `.ff-endgame-custom-row` — แถวกรอกมอน (ยุบเป็น 2 คอลัมน์ที่ ≤640px)
- `.ff-endgame-actions` — ปุ่มล่าง (เต็มความกว้างบนมือถือ)
ค่าเป้าหมายมือถือ: ปุ่มสูง ≥44–46px, ไม่มี horizontal scroll ระดับหน้า
### 3.12 Cache busting (สำคัญ)
เปลี่ยนไฟล์แล้วต้อง bump `?v=`:
| ไฟล์ | เวอร์ชันใหม่ |
| :--- | :--- |
| `css/styles.css` | `2.36` |
| `js/app.js` | `3.32` |
| `js/i18n.js` | `2.26` (ทุก importer ต้องตรงกัน) |
| `js/endgame.js` | `1` (ไฟล์ใหม่) |
Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทาง `app.js`), `js/app.js`, `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js`
---
## 4. ไฟล์ที่เพิ่ม / แก้ในรอบนี้
### เพิ่มใหม่
| ไฟล์ | รายละเอียด |
| :--- | :--- |
| `js/endgame.js` | pure helpers สำหรับเลือกห้อง + สร้าง `battle_config` |
| `scripts/build-firefly-endgame-data.cjs` | สร้างแคตตาล็อกจาก API ต้นฉบับ |
| `assets/firefly-endgame.json` | แคตตาล็อกพร้อมใช้ (~604 KB) |
| `tests/endgame.test.cjs` | unit tests ของ endgame helpers (8 tests) |
### แก้ไข
| ไฟล์ | สิ่งที่แก้ |
| :--- | :--- |
| `index.html` | แบ่ง `#monster-settings-gac` / `#monster-settings-firefly`, เพิ่ม UI picker, bump `?v=` |
| `js/app.js` | import endgame, state ใหม่, ฟังก์ชัน picker ทั้งหมด, branch ตามโหมด, เปลี่ยน title |
| `js/modes.js` | เพิ่ม capability `fireflyEndgame` |
| `js/firefly.js` | normalize `blessings` / `monsters` ให้ตรง zod |
| `js/i18n.js` | เพิ่ม 46 คีย์ th/en, bump version |
| `css/styles.css` | สไตล์ `.ff-endgame-*` + responsive |
| `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js` | bump `i18n.js?v=2.26` (และ bump version ของ module ปลายทางใน `app.js`) |
| `tests/firefly-payload.test.cjs` | อัปเดต expected shape + เพิ่ม test normalize |
| `tests/mode-capabilities.test.cjs` | เพิ่ม `fireflyEndgame`, คีย์ i18n, DOM hooks |
---
## 5. สิ่งที่เสร็จแล้ว (ละเอียด)
### 5.1 โหมดเชื่อมต่อ + transport
- [x] `js/modes.js` capability matrix ครบ 3 โหมด
- [x] `fireflySyncUrl()` แปลง base URL → `/sync`
- [x] `fireflyRequest()` ไม่ส่ง custom header, มี timeout, parse error body
- [x] `probeFirefly()` = connect probe (ไม่ส่ง `data`)
- [x] `pushFireflySync()` = sync เต็ม
- [x] `refreshStatus()` แยกพฤติกรรม firefly (local-only)
- [x] กัน `tool-session`, `pagehide`, `fetchLatestBattleReport` ในโหมดที่ไม่รองรับ
### 5.2 extra_data
- [x] `normalizeExtraData()` ตรวจ/เติมทุกบล็อก
- [x] ฟอร์มใน `#card-firefly-extras` (multi_path, setting, challenge, theory_craft, lua)
- [x] `readFireflyExtrasForm()` / `renderFireflyExtras()`
- [x] Creative Mode = `theory_craft.mode` ใช้ state เดียว (`state.creativeMode`)
- [x] ทุกจุดที่เปลี่ยน Creative Mode วิ่งผ่าน `setCreativeMode()`
- [x] factory reset ล้าง `state.fireflyExtraData`
### 5.3 Enka import
- [x] `js/enka-import.js` แปลง Enka → FreeSR (avatars, lightcones, relics)
- [x] UI ในแท็บ JSON ใช้ได้ทุกโหมด
- [x] proxy `/api/enka/:uid` (dev server + `vercel.json` rewrite)
- [x] paste JSON สำรองเมื่อ proxy ใช้ไม่ได้
### 5.4 UI ตามโหมด
- [x] ซ่อน tab/card ที่ backend ไม่รองรับ
- [x] ไม่ค้างอยู่แท็บที่ถูกซ่อน
- [x] JSON editor จำกัด mode ตาม backend
- [x] badge persistence / API URL hint เปลี่ยนตามโหมด
- [x] `setupServerModes()` ผูก event ครั้งเดียว
### 5.5 Firefly Endgame Picker
- [x] ดึง logic แปลงข้อมูลจาก bundle ต้นฉบับครบทุกโหมด
- [x] สร้างแคตตาล็อกแบบ static (ไม่ต้องพึ่ง API ต้นทางตอนใช้งาน)
- [x] UI เลือก season / floor / side / stage
- [x] UI Custom Enemy (wave + monster + level + amount)
- [x] preview `battle_config` ก่อนใช้
- [x] เปิด picker ที่ห้องเดิมจาก `battle_config`
- [x] Apply → เขียน `battle_config` (ทดสอบครบ MOC/PF/AS/PEAK/CE)
- [x] Peak → อัปเดต `extra_data.challenge`
- [x] Apply & sync → `POST /sync` (error path ทดสอบแล้ว, happy path ติดที่ยังไม่มีเซิร์ฟเวอร์)
- [x] i18n th/en + รีเรนเดอร์เมื่อเปลี่ยนภาษา (แก้บั๊กสถานะค้างเป็น loading แล้ว)
- [x] responsive CSS (ทดสอบจริง 360px / 768px)
- [x] เรียง season/floor ใหม่สุด–สูงสุดขึ้นก่อน
### 5.6 Tests
```
node --test tests/*.test.cjs
ℹ tests 44
ℹ pass 44
ℹ fail 0
```
ครอบคลุม:
- โครงสร้าง payload `/sync` (avatars/lightcones/relics/battle_config)
- การ normalize `blessings` / `monsters` (รวม `dynamic_key`)
- capability matrix + i18n keys + DOM hooks
- endgame helpers: default/resolve/build/challenge/find-by-stage/custom
---
## 6. สิ่งที่ยังเหลือ (ละเอียด + วิธีทำ)
### 6.1 ✅ ทดสอบ Apply / Apply & sync ในเบราว์เซอร์
**สถานะ:** ทดสอบครบทุกโหมดแล้วเมื่อ 2026-11-04 ที่ `http://localhost:5173` โดยโหลด FreeSR JSON ขั้นต่ำผ่านแท็บ JSON ก่อน
**ผลทดสอบจริง:**
- MoC → `battle_type: "MOC"`, `stage_id: 30127121`, `cycle_count: 30`, `monsters` เป็น array ของ array ของ `{monster_id, level, amount}`
- PF → `battle_type: "PF"`, `stage_id: 30326041`, `cycle_count: 4`, `blessings: 1`, 3 waves
- AS → `battle_type: "AS"`, `stage_id: 420564`, `blessings: 2`, 1 wave
- Anomaly Arbitration → `battle_type: "PEAK"`, `stage_id: 30511021`, `blessings: 3`, และ `extra_data.challenge.challenge_peak_group_id = 11` ถูกเขียนลง `localStorage['gac_srtool_firefly_extra']`
- Custom Enemy → เพิ่ม wave/มอนแล้ว Apply ได้ `battle_type: "CE"`, `stage_id: 0`, `cycle_count` ตามที่กรอก
- รีโหลดหน้า → picker กลับมาที่ห้องเดิมจาก `syncFireflyEndgameSelectionFromSave` (ทดสอบกับ Custom: กลับมาเป็น CE และ wave ตรงกับที่บันทึก)
- Apply & sync ตอนไม่มี FireflyGo → สถานะเป็น `Sync failed: Failed to fetch` (data-state = error) ไม่มี crash และ cache ถูกบันทึกก่อน sync
- เปลี่ยนภาษาไทย→อังกฤษขณะอยู่หน้า picker → สถานะเปลี่ยนเป็น `battle_config saved locally` (ไม่ค้างที่ข้อความ loading) หลังแก้บั๊กในข้อ 6.1.1
### 6.1.1 🐞 บั๊กที่พบและแก้แล้วระหว่าง audit
`#ff-endgame-status` มี `data-i18n="ffEndgameLoading"` ทำให้ `applyTranslations()` เขียนทับข้อความสถานะเป็น "กำลังโหลด…" ทุกครั้งที่เปลี่ยนภาษา แต่ `renderFireflyEndgame()` ไม่คืนค่าสถานะที่เก็บไว้
**แก้โดย:** เพิ่ม `applyFireflyEndgameStatus()` ที่อ่าน `state.fireflyEndgame.statusKey/statusState/statusVars` แล้วเรียกที่ท้าย `renderFireflyEndgame()` ทั้งสอง branch (ทั้งตอน catalog ยังไม่โหลดและตอนเรนเดอร์ปกติ) — ตอนนี้สถานะถูกต้องหลังสลับภาษา
### 6.2 ✅ ทดสอบมือถือ 360–768px
**สถานะ:** ทดสอบด้วย iframe ขนาด 360×800 และ 768×1024 (media query ภายใน iframe ทำงานเหมือน viewport จริง)
**ผลทดสอบจริง:**
| ตัวชี้วัด | 360px | 768px |
| :--- | :--- | :--- |
| `documentElement.scrollWidth <= clientWidth` | 345 = 345 (ไม่มี horizontal scroll) | 753 = 753 |
| ความสูง tab `#ff-endgame-tabs button` | 46px | 46px |
| ความสูงปุ่ม Apply ทั้ง 3 | 46px | 46px |
| ความสูง `.ff-endgame-side` | 56px | 56–71px |
| ความสูง select | 44px | — |
| `.ff-endgame-picker` | 1 คอลัมน์ (319px) | 2 คอลัมน์ (340px + 340px) |
| `.ff-endgame-custom-row` | 2 คอลัมน์ (138px + 138px) | — |
โหมด GaCy เดิมยังทำงาน: `#monster-settings-gac` แสดง, `#monster-settings-firefly` ถูกซ่อน, แท็บ save manager กลับมา, `#card-firefly-extras` ถูกซ่อน
### 6.3 🚫 ทดสอบ `POST /sync` กับ FireflyGO จริง
**ติดเพราะ:** ไม่มีเซิร์ฟเวอร์ Firefly_Srtools รันที่ `localhost:21000`
**เมื่อมีเซิร์ฟเวอร์:**
1. ตั้ง API URL เป็น `http://localhost:21000`
2. กด Connect → ต้องได้ `extra_data` กลับมา
3. Sync → ตรวจไฟล์/สถานะฝั่งเซิร์ฟเวอร์
4. ตรวจว่า `battle_config` ที่ส่งผ่าน zod (ไม่มี warning เรื่อง schema)
### 6.4 ✅ เรียงลำดับ season และ floor ให้ใหม่สุด/สูงสุดอยู่บน
**สถานะ:** ดำเนินการเรียบร้อยแล้ว ใน `renderFireflyRoomEditor()` ทั้ง `groups` และ `levels` ถูกเรียงลำดับจากมาก→น้อย (descending) ทำให้ซีซันใหม่สุดและชั้น 12 อยู่ด้านบนสุดของ Dropdown สะดวกต่อการเลือกใช้งานทันที
### 6.5 ⏳ ตรวจ `docs/` และ commit
- `docs/` เป็น untracked ตาม `AGENTS.md` — ไฟล์นี้ถูกสร้างตามคำขอของผู้ใช้
- ยังไม่ commit งานรอบนี้
- ต้องตัดสินใจว่าจะ push ขึ้น `origin/test-gay` หรือไม่ (ต้องถามก่อน)
### 6.6 ⏳ (อนาคต) อัปเดตแคตตาล็อกเมื่อเกมออก season ใหม่
```bash
node scripts/build-firefly-endgame-data.cjs
```
แล้ว bump `?v=` ของ `assets/firefly-endgame.json` ถ้ามีการอ้างอิงในอนาคต (ปัจจุบัน fetch ตรงด้วย `cache: 'force-cache'`)
---
## 7. ข้อจำกัด / ข้อควรระวัง
1. **แคตตาล็อกเป็น snapshot**: ถ้า Firefly SrTools อัปเดตข้อมูล ต้องรันสคริปต์ใหม่
2. **ไอคอนมอนเป็น CDN ภายนอก** (`cdn.punklorde.org`) — ถ้า CDN ล่มจะเห็นอิโมจิ fallback
3. **Simulated Universe ไม่อยู่ในขอบเขต** — ต้นฉบับมี แต่รอบนี้ยังไม่ทำ
4. **`amount` ถูกเซ็ตเป็น 1 เสมอ** สำหรับห้องปกติ (custom แก้ได้)
5. **`extra_data.challenge` อัปเดตเฉพาะโหมด peak** — โหมดอื่นไม่แตะค่าเดิม
6. **เขียนทับ `battle_config` ใน local cache**: ถ้าสลับกลับไปโหมด GaCy แล้วใช้ custom wave editor ค่า `amount` จะไม่ถูกอ่าน (GaCy ใช้ `max_hp`) — แต่ GaCy จะโหลด catalog จากเซิร์ฟเวอร์ใหม่เมื่อเชื่อมต่อ
7. **`normalizeBattleConfig()` ตัด key ที่ไม่รู้จัก** รวมถึง `custom_battle_lineup`
8. **ห้ามใส่ header เพิ่มใน `/sync`** เพราะจะทำให้ preflight fail
---
## 8. คู่มือรัน/ตรวจสอบ
### Unit tests
```bash
node --test tests/*.test.cjs
```
### Dev server (มี Enka proxy)
```bash
npm run dev:proxy
# หรือ
node scripts/dev-server.cjs
```
เปิด `http://localhost:5173`
> ถ้าเจอ `EADDRINUSE` ให้ปิด process ที่ถือพอร์ต 5173 ก่อน (`Get-NetTCPConnection -LocalPort 5173`)
### ตรวจ payload ก่อนซิงค์
- เปิด DevTools → Network → กรอง `/sync`
- ดู Request Payload: ต้องมี `data.battle_config.blessings` เป็น object และ `monsters` เป็น array ของ array ของ object
### ตรวจ i18n
- สลับภาษาไทย/อังกฤษ ขณะอยู่หน้า Monster Setting
- ชื่อ season, ชื่อมอน, สถานะ, ปุ่ม ต้องเปลี่ยนตามภาษา
- ภาษาที่ไม่มีการแปลจะ fallback เป็นอังกฤษ
---
## 9. Checklist ก่อน commit
- [x] `node --test tests/*.test.cjs` ผ่านทั้งหมด
- [x] เปิดเว็บโหมด firefly → Monster Setting แสดง picker
- [x] Apply เขียน `battle_config` ถูกต้อง (MOC/PF/AS/PEAK/CE)
- [x] Apply & sync แสดง error ที่แปลแล้วเมื่อไม่มีเซิร์ฟเวอร์
- [x] เปลี่ยนภาษาแล้วหน้า picker รีเรนเดอร์ และสถานะไม่ค้างที่ loading
- [x] มือถือ 360px ไม่มี horizontal scroll และปุ่ม ≥44px
- [x] โหมด GaCy เดิมยังทำงาน (Monster Setting เดิม, Aha's Help, custom waves)
- [x] `?v=` ถูก bump ครบ (`styles.css 2.36`, `app.js 3.32`, `i18n.js 2.26` ทุก importer)
- [x] ไม่มีข้อความ hardcode ใหม่ใน HTML/JS
- [x] ไม่แก้ `README.md`
- [ ] ทดสอบ `POST /sync` happy path กับ FireflyGO จริง (ต้องมีเซิร์ฟเวอร์)
+289
View File
@@ -0,0 +1,289 @@
# เอกสารข้อมูลจำเพาะและการเชื่อมต่อ API: March7thHoney
> **Firefly_Srtools / SR-Tools to March7thHoney Integration Guide**
เอกสารฉบับนี้อธิบายรายละเอียดทางเทคนิคของโปรโตคอลการเชื่อมต่อ API ระหว่างเครื่องมือจัดการตัวละคร (**Firefly_Srtools / SR-Tools**) และเซิร์ฟเวอร์จำลอง **March7thHoney** (Honkai: Star Rail Private Server) โดยอ้างอิงจาก Source Code ล่าสุดของโปรเจกต์ `Firefly_Srtools`
---
## 1. ภาพรวมสถาปัตยกรรม (Architecture Overview)
ระบบการเชื่อมต่อแบบ **March7thHoney** ได้รับการออกแบบมาสำหรับการเชื่อมต่อแบบ Client-to-Server ผ่านทาง REST API โดยมีลำดับขั้นตอนหลัก 2 สถานะ:
1. **Connection Probe (Handshake):** ตรวจสอบการมีอยู่ของเซิร์ฟเวอร์ และยืนยันตัวตน (Authentication)
2. **Data Synchronization:** ส่งข้อมูลการจัดทีม, ตัวละคร, กรวยแสง, และรีลิกส์ (FreeSR Data Payload) เข้าสู่เซิร์ฟเวอร์
### ไดอะแกรมลำดับการทำงาน (Sequence Diagram)
```mermaid
sequenceDiagram
autonumber
actor User as ผู้ใช้งาน (Web UI)
participant Client as Firefly_Srtools (Browser)
participant Server as March7thHoney Server
Note over User,Client: ขั้นตอนที่ 1: ตรวจสอบการเชื่อมต่อ (Connect PS)
User->>Client: กรอก Server URL, Username, Password แล้วกด "Connect PS"
Client->>Client: Normalizes URL (เติม scheme + ต่อท้าย /sr-tools)
Client->>Server: POST /sr-tools { username, password }
Server-->>Client: HTTP 200 OK { status: 200, message: "OK" }
Client-->>User: แสดงสถานะ 🟢 Connected
Note over User,Client: ขั้นตอนที่ 2: ซิงค์ข้อมูลเข้าเกม (Sync)
User->>Client: จัดทีม/แก้ไขตัวละคร แล้วกด "Sync"
Client->>Client: แปลงข้อมูลเป็น FreeSR JSON Schema
Client->>Server: POST /sr-tools { username, password, data: FreeSRJson }
Server->>Server: บันทึกข้อมูลลงฐานข้อมูล/หน่วยความจำของเซิร์ฟเวอร์
Server-->>Client: HTTP 200 OK { status: 200, message: "Sync Success" }
Client-->>User: แจ้งเตือน ซิงค์ข้อมูลสำเร็จ
```
---
## 2. ข้อมูลจำเพาะของ Endpoint (Endpoint Specification)
* **Path Suffix:** `/sr-tools`
* **HTTP Method:** `POST`
* **Request Content-Type:** `application/json`
* **Accept:** `application/json, text/plain`
### กฎการจัดรูปแบบ URL (URL Normalization Rules)
ในฟังก์ชัน `buildConnectUrl('March7thHoney', serverUrl)` ของระบบ:
1. **การเติม Scheme:** หากผู้ใช้ไม่ได้ระบุโปรโตคอล (`http://` หรือ `https://`) ระบบจะเติม `http://` ให้โดยอัตโนมัติ
2. **การตัด Trailing Slashes:** ตัดเครื่องหมาย `/` ส่วนเกินที่ท้าย URL ออก
3. **การตรวจสอบ Path ท้าย:** ตรวจสอบว่า URL ลงท้ายด้วย `/sr-tools` (Case-Insensitive) หรือไม่ หากยังไม่มีจะทำการเติม `/sr-tools` ต่อท้าย
**ตัวอย่างการแปลง URL:**
| ค่าที่ผู้ใช้กรอกใน UI | URL ปลายทางที่ระบบยิง Request |
| :--- | :--- |
| `localhost:21000` | `http://localhost:21000/sr-tools` |
| `http://127.0.0.1:21000/` | `http://127.0.0.1:21000/sr-tools` |
| `https://ps.example.com` | `https://ps.example.com/sr-tools` |
| `http://192.168.1.50:21000/sr-tools` | `http://192.168.1.50:21000/sr-tools` (ไม่เติมซ้ำ) |
---
## 3. รูปแบบข้อมูล Request Payload (Request Schemas)
### 3.1 การเชื่อมต่อครั้งแรก (Handshake / Connection Probe)
เมื่อผู้ใช้กดปุ่ม **"Connect PS"** จะเป็นการส่ง Request เพื่อตรวจสอบความถูกต้องของ URL และบัญชีผู้ใช้:
```json
{
"username": "10001",
"password": "UserPassword123"
}
```
* `username` *(string, required)*: ชื่อผู้ใช้ หรือ UID บัญชีในเกม
* `password` *(string, required)*: รหัสผ่านของบัญชีผู้ใช้
* `data` *(undefined)*: ในการ Probe เชื่อมต่อครั้งแรก จะไม่ส่งฟิลด์นี้
> [!NOTE]
> **การรักษาความปลอดภัยของรหัสผ่าน:** รหัสผ่านจะถูกจัดเก็บไว้ใน `sessionStorage` ชั่วคราวเท่านั้น และจะไม่ถูกเขียนลง `localStorage` ถาวร เพื่อป้องกันการถูกดึงข้อมูลเมื่อปิดเบราว์เซอร์
---
### 3.2 การส่งข้อมูลเข้าเซิร์ฟเวอร์ (Data Synchronization)
เมื่อผู้ใช้กดปุ่ม **"Sync"** ข้อมูลทีมและตัวละครจะถูกรวบรวมเป็นก้อน `FreeSRJson` แนบไปด้วย:
```json
{
"username": "10001",
"password": "UserPassword123",
"data": {
"avatars": [
{
"avatar_id": 1307,
"level": 80,
"rank": 6,
"promotion": 6,
"skills": {
"1307001": 9,
"1307002": 10,
"1307003": 10,
"1307004": 10
},
"equipment": {
"tid": 23014,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
{
"tid": 61121,
"level": 15,
"main_affix_id": 1,
"sub_affixes": [
{ "sub_affix_id": 1, "cnt": 2, "step": 1 },
{ "sub_affix_id": 2, "cnt": 3, "step": 2 }
]
}
]
}
],
"battle_type": "Stage",
"stage_id": 1001,
"lineup": [1307]
}
}
```
> [!IMPORTANT]
> **ความแตกต่างระหว่าง March7thHoney และ FireflyGo:**
> โหมด `March7thHoney` จะส่งเพียง 3 ฟิลด์คือ `{ username, password, data }` เท่านั้น จะ**ไม่มี**การส่งก้อน `extra_data` (เช่น `multi_path` ของตัวเอก/March 7th หรือ `lua` script) ไปยังเซิร์ฟเวอร์
---
## 4. ข้อกำหนดการตอบกลับของ Server (Server Response Specification)
เพื่อให้ Client แสดงผลว่าเชื่อมต่อสำเร็จ เซิร์ฟเวอร์ต้องตอบกลับตามเกณฑ์ดังต่อไปนี้:
### 4.1 เกณฑ์การเชื่อมต่อสำเร็จ (Success Condition)
1. **HTTP Status Code:** ต้องอยู่ในช่วง `200` ถึง `299`
2. **Content Format:** ต้องเป็น JSON หรือ Text (ห้ามขึ้นต้นด้วย Tag HTML `<`)
3. **Response Body:** ไม่มีฟิลด์ `error` (หรือ `error` เป็นสตริงว่าง)
**ตัวอย่าง Response ที่ถูกต้อง:**
```json
{
"status": 200,
"message": "Connection established successfully"
}
```
หรือสามารถส่งสถิติสรุปตัวละครกลับมาได้:
```json
{
"status": 200,
"message": "Sync successful",
"stats": {
"avatars": 4,
"created": 1,
"updated": 3,
"deleted": 0
}
}
```
---
### 4.2 เกณฑ์การเชื่อมต่อล้มเหลว (Failure Condition)
Client จะถือว่าการเชื่อมต่อล้มเหลว (`success: false`) ทันทีเมื่อ:
* **HTTP Status >= 400:** เช่น 401 Unauthorized, 404 Not Found, 500 Internal Server Error
* **มี Error Field ใน JSON:** แม้ HTTP Status จะเป็น 200 แต่ถ้ามี `"error": "Invalid password"` จะถูกลดระดับเป็นสถานะ 400
* **ได้หน้า HTML กลับมา (HTML Detection):** หากเซิร์ฟเวอร์ส่งหน้าเว็บ 404/502 ของ Nginx หรือ Cloudflare ที่มีแท็ก `<html>` ฟังก์ชัน `looksLikeHtml()` จะดักจับและตัดการเชื่อมต่อทันที
---
## 5. ตัวอย่างการสร้างเซิร์ฟเวอร์รองรับ Endpoint `/sr-tools`
### 5.1 ตัวอย่างด้วย Node.js (Express)
```javascript
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Endpoint สำหรับ March7thHoney
app.post('/sr-tools', (req, res) => {
const { username, password, data } = req.body;
// 1. ตรวจสอบข้อมูลการเข้าสู่ระบบ
if (!username || password !== 'ExpectedPassword') {
return res.status(401).json({
status: 401,
error: 'Invalid username or password'
});
}
// 2. กรณี Handshake (Connect PS) - ยังไม่มี data ส่งมา
if (!data) {
return res.status(200).json({
status: 200,
message: 'March7thHoney handshake successful'
});
}
// 3. กรณี Sync Data - บันทึกข้อมูลตัวละคร
console.log(`Received FreeSR lineup for ${username}:`, data.avatars?.length, 'avatars');
// บันทึกลง Database / Memory ของเซิร์ฟเวอร์...
return res.status(200).json({
status: 200,
message: 'Data synchronized successfully',
stats: {
avatars: data.avatars?.length || 0,
created: 0,
updated: data.avatars?.length || 0,
deleted: 0
}
});
});
app.listen(21000, () => console.log('March7thHoney SR-Tools API listening on port 21000'));
```
### 5.2 ตัวอย่างด้วย Python (FastAPI)
```python
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, Any, Dict
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
class SRToolsPayload(BaseModel):
username: str
password: str
data: Optional[Dict[str, Any]] = None
@app.post("/sr-tools")
async def handle_sr_tools(payload: SRToolsPayload):
# ยืนยันตัวตน
if payload.username != "10001" or payload.password != "SecretPass":
raise HTTPException(status_code=401, detail="Authentication failed")
# Handshake
if payload.data is None:
return {"status": 200, "message": "Handshake OK"}
# Sync Data
avatars_count = len(payload.data.get("avatars", []))
return {
"status": 200,
"message": "Data synchronized successfully",
"stats": {"avatars": avatars_count, "created": 0, "updated": avatars_count, "deleted": 0}
}
```
---
## 6. ตารางเปรียบเทียบ Connection Types ใน Firefly_Srtools
| รูปแบบการเชื่อมต่อ | URL Endpoint เริ่มต้น | ต้องกรอก URL เอง? | ต้องกรอก Password? | รูปแบบ Payload |
| :--- | :--- | :---: | :---: | :--- |
| **FireflyGo** | `http://localhost:21000/sync` | ❌ อัตโนมัติ | ❌ ไม่ต้อง | ส่ง `extra_data` (Multi-path, Lua) |
| **RobinSR** | `http://localhost:21000/srtools` | ❌ อัตโนมัติ | ❌ ไม่ต้อง | ส่ง `extra_data` |
| **March7thHoney** | `{Server_URL}/sr-tools` | ต้องกรอก | ต้องกรอก | `{ username, password, data }` |
| **Collapse-RPG** | `{Server_URL}/query_connection` | ต้องกรอก | ต้องกรอก | แปลงเป็น `CollapseAvatarData` Schema |
| **Other** | `{Server_URL}` | ต้องกรอก | ต้องกรอก | ส่งแบบ Raw FreeSRJson หรือผ่าน Proxy |
---
## 7. ข้อควรระวังและการแก้ปัญหา (Troubleshooting)
1. **ปัญหา CORS (Cross-Origin Resource Sharing):**
* หากเข้าใช้งาน Firefly_Srtools ผ่านเบราว์เซอร์บน Web Domain แต่ Server ของ March7thHoney รันอยู่ที่เครื่องอื่น ต้องเปิด Header `Access-Control-Allow-Origin: *` บนเซิร์ฟเวอร์
2. **ปัญหา Mixed Content (HTTP vs HTTPS):**
* หากหน้าเว็บ Firefly_Srtools เปิดด้วย `https://` แต่เซิร์ฟเวอร์ March7thHoney เป็น `http://` เบราว์เซอร์จะบล็อกการยิง Request อัตโนมัติ ต้องใช้งานผ่าน `http://` เหมือนกัน หรือติดตั้ง SSL ให้กับเซิร์ฟเวอร์
3. **ปัญหา Private Host / Localhost:**
* หากใช้งานโหมด Proxy ของเว็บ โฮสต์ที่เป็น Local IP (192.168.x.x, 127.0.0.1) จะยิงไม่ผ่าน ต้องเชื่อมต่อแบบ Local Mode เท่านั้น