Spaces:
Running
fix(crash): useSyncExternalStore thiếu getSnapshot — 'b is not a function' khi vào hang
Browse filesRoot cause: store.js gọi useSync(c) chỉ với 1 tham số (subscribe), thiếu
getSnapshot. React useSyncExternalStore gọi getSnapshot() = undefined()
-> TypeError 'b is not a function' -> CaveErrorBoundary -> màn hình lỗi
khi vào hang, không thấy cửa hang / người chơi (áo đỏ).
Fix: wrapper useSyncStore(subscribe, getSnapshot) cho cả 10 hook
(chat/invites/talk/duel/penalty/tug/ball/voice/notice/rage) — đúng như
bản gốc hangmu.online: useSyncExternalStore(ql.subscribe, ql.getSnapshot).
Kèm:
- patch_backend.mjs: chèn /api/log + /api/logs TRƯỚC catch-all
@app .get('/{path:path}') để đọc được client errors (trước đây bị route
catch-all nuốt -> trả index.html thay vì JSON)
- index.html: cache-bust bundle v=5 (bản cũ v=4 bị browser/service worker
giữ lại bundle crash)
- frontend/index.html +1 -1
- frontend/store.js +22 -11
- patch_backend.mjs +22 -4
|
@@ -93,6 +93,6 @@
|
|
| 93 |
<body>
|
| 94 |
<div id="root"></div>
|
| 95 |
<!-- Bundle: app.bundle.js tự chứa toàn bộ (react, three, zustand) -->
|
| 96 |
-
<script src="/static/app.bundle.js?v=
|
| 97 |
</body>
|
| 98 |
</html>
|
|
|
|
| 93 |
<body>
|
| 94 |
<div id="root"></div>
|
| 95 |
<!-- Bundle: app.bundle.js tự chứa toàn bộ (react, three, zustand) -->
|
| 96 |
+
<script src="/static/app.bundle.js?v=5"></script>
|
| 97 |
</body>
|
| 98 |
</html>
|
|
@@ -207,17 +207,28 @@ function makeRage() {
|
|
| 207 |
}
|
| 208 |
}
|
| 209 |
|
| 210 |
-
/* ---- hooks ----
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
/* =========================================================================
|
| 222 |
WebSocket client — giao thức JSON (phía server là backend.py)
|
| 223 |
========================================================================= */
|
|
|
|
| 207 |
}
|
| 208 |
}
|
| 209 |
|
| 210 |
+
/* ---- hooks ----
|
| 211 |
+
useSyncExternalStore(subscribe, getSnapshot) — phải truyền ĐÚNG 2 hàm
|
| 212 |
+
(bản gốc hangmu.online: `useSyncExternalStore(ql.subscribe, ql.getSnapshot)`).
|
| 213 |
+
Trước đây gọi `useSync(c)` (thiếu getSnapshot) → React gọi getSnapshot()
|
| 214 |
+
= undefined() → "TypeError: b is not a function" khi vào hang (toàn bộ
|
| 215 |
+
màn hang crash). Fix: wrapper bọc subscribe/getSnapshot của sub-store. */
|
| 216 |
+
function useSyncStore(storeObj) {
|
| 217 |
+
return useSync(
|
| 218 |
+
(cb) => storeObj.subscribe(cb),
|
| 219 |
+
() => storeObj.getSnapshot(),
|
| 220 |
+
)
|
| 221 |
+
}
|
| 222 |
+
export function useChat() { return useSyncStore(useGame((s) => s.chat)) }
|
| 223 |
+
export function useInvites() { return useSyncStore(useGame((s) => s.invites)) }
|
| 224 |
+
export function useTalk() { return useSyncStore(useGame((s) => s.talk)) }
|
| 225 |
+
export function useDuel() { return useSyncStore(useGame((s) => s.duel)) }
|
| 226 |
+
export function usePenalty() { return useSyncStore(useGame((s) => s.penalty)) }
|
| 227 |
+
export function useTug() { return useSyncStore(useGame((s) => s.tug)) }
|
| 228 |
+
export function useBall() { return useSyncStore(useGame((s) => s.ball)) }
|
| 229 |
+
export function useVoice() { return useSyncStore(useGame((s) => s.voice)) }
|
| 230 |
+
export function useNotice() { return useSyncStore(useGame((s) => s.notice)) }
|
| 231 |
+
export function useRage() { return useSyncStore(useGame((s) => s.rage)) }
|
| 232 |
/* =========================================================================
|
| 233 |
WebSocket client — giao thức JSON (phía server là backend.py)
|
| 234 |
========================================================================= */
|
|
@@ -6,10 +6,28 @@ import fs from 'node:fs'
|
|
| 6 |
|
| 7 |
const path = 'backend.py'
|
| 8 |
let s = fs.readFileSync(path, 'utf8')
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
if (idx === -1) {
|
| 12 |
-
console.error('PATCH FAILED:
|
| 13 |
process.exit(1)
|
| 14 |
}
|
| 15 |
const inject = `# ============ Client error log (từ ErrorBoundary / window.onerror) ============
|
|
@@ -31,4 +49,4 @@ async def api_logs():
|
|
| 31 |
`
|
| 32 |
s = s.slice(0, idx) + inject + s.slice(idx)
|
| 33 |
fs.writeFileSync(path, s)
|
| 34 |
-
console.log(
|
|
|
|
| 6 |
|
| 7 |
const path = 'backend.py'
|
| 8 |
let s = fs.readFileSync(path, 'utf8')
|
| 9 |
+
// IMPORTANT: phải chèn TRƯỚC catch-all @app.get("/{path:path}") (serve_static) —
|
| 10 |
+
// nếu chèn sau, FastAPI match route theo thứ tự đăng ký → GET /api/logs bị
|
| 11 |
+
// catch-all nuốt, trả index.html thay vì JSON (không đọc được client errors).
|
| 12 |
+
const markers = [
|
| 13 |
+
'@app.get("/{path:path}")', // nơi lý tưởng: chèn ngay trước catch-all
|
| 14 |
+
'if __name__ == "__main__":', // fallback cũ (chỉ dùng nếu không tìm thấy catch-all)
|
| 15 |
+
]
|
| 16 |
+
let idx = -1
|
| 17 |
+
let used = -1
|
| 18 |
+
for (let i = 0; i < markers.length; i++) {
|
| 19 |
+
const m = markers[i]
|
| 20 |
+
const at = s.indexOf(m)
|
| 21 |
+
if (at !== -1) {
|
| 22 |
+
// tìm đầu dòng để chèn sạch sẽ
|
| 23 |
+
const lineStart = s.lastIndexOf('\n', at) + 1
|
| 24 |
+
idx = lineStart
|
| 25 |
+
used = i
|
| 26 |
+
break
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
if (idx === -1) {
|
| 30 |
+
console.error('PATCH FAILED: marker not found')
|
| 31 |
process.exit(1)
|
| 32 |
}
|
| 33 |
const inject = `# ============ Client error log (từ ErrorBoundary / window.onerror) ============
|
|
|
|
| 49 |
`
|
| 50 |
s = s.slice(0, idx) + inject + s.slice(idx)
|
| 51 |
fs.writeFileSync(path, s)
|
| 52 |
+
console.log(`patched backend.py ok: /api/log + /api/logs (inserted at marker [${used}])`)
|