bep40 commited on
Commit
230dc10
·
1 Parent(s): 331f563

fix(crash): useSyncExternalStore thiếu getSnapshot — 'b is not a function' khi vào hang

Browse files

Root 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)

Files changed (3) hide show
  1. frontend/index.html +1 -1
  2. frontend/store.js +22 -11
  3. patch_backend.mjs +22 -4
frontend/index.html CHANGED
@@ -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=4"></script>
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>
frontend/store.js CHANGED
@@ -207,17 +207,28 @@ function makeRage() {
207
  }
208
  }
209
 
210
- /* ---- hooks ---- */
211
- export function useChat() { const c = useGame((s) => s.chat); return useSync(c) }
212
- export function useInvites() { return useSync(useGame((s) => s.invites)) }
213
- export function useTalk() { return useSync(useGame((s) => s.talk)) }
214
- export function useDuel() { return useSync(useGame((s) => s.duel)) }
215
- export function usePenalty() { return useSync(useGame((s) => s.penalty)) }
216
- export function useTug() { return useSync(useGame((s) => s.tug)) }
217
- export function useBall() { return useSync(useGame((s) => s.ball)) }
218
- export function useVoice() { return useSync(useGame((s) => s.voice)) }
219
- export function useNotice() { return useSync(useGame((s) => s.notice)) }
220
- export function useRage() { return useSync(useGame((s) => s.rage)) }
 
 
 
 
 
 
 
 
 
 
 
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
  ========================================================================= */
patch_backend.mjs CHANGED
@@ -6,10 +6,28 @@ import fs from 'node:fs'
6
 
7
  const path = 'backend.py'
8
  let s = fs.readFileSync(path, 'utf8')
9
- const marker = 'if __name__ == "__main__":'
10
- const idx = s.indexOf(marker)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  if (idx === -1) {
12
- console.error('PATCH FAILED: main marker not found')
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('patched backend.py ok: /api/log + /api/logs')
 
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}])`)