chore(#207): 通知タップ切り分け用 診断ロガー [DO NOT MERGE] - #216
Conversation
通知タップ時の挙動を Firebase デフォルトから明示ハンドラに置き換え、既存
PWA/tab を単一 tab のまま該当 block まで scroll できるようにする。
- SW: notificationclick で clients.matchAll → focused > visible > 任意
の優先度で client を選び、postMessage で SPA navigate。無ければ
clients.openWindow。別 origin URL は payload 経路含め弾く
- client 側: useFcmNavigationListener が SW message を受け React Router
で navigate。WindowClient.navigate() のフルリロードを避けて atom /
SWR / scroll 位置を保持
- useFocusBlockOnMount: ?focusBlock={id} を useBlock で解決 → pageId
切替 → data-block-id の DOM を rAF 待機して scrollIntoView (center)
→ replaceState で query 除去。404 / 非数値ゴミは黙って掃除
- useBlocks: list fetch 時に個別 key を populate し useBlock の重複
fetch を回避 (楽観更新値がある場合は skip して巻き戻し防止)
- View block 2 種に data-block-id を付与 (edit モードは対象外)
- useFcmNavigationListener: FCM_NAVIGATE 受信で navigate 呼び出し / 別 origin & 非 http scheme & 型不正 payload の無視 / unmount cleanup / serviceWorker 未定義環境 - useFocusBlockOnMount: block ロード成功で selectedPageId 切替 + scrollIntoView + query 除去 / 404 → query 除去のみ / 非数値 → API も叩かず query 除去 / focusBlock 無 → no-op
- docs/notifications.md: §5 Deep link を実装済み記述に更新 (SW ハンドラ → SPA navigate → useFocusBlockOnMount → scrollIntoView の経路と、 処理後の replaceState 除去まで明記) - docs/memo/notification_roadmap.md: P6b を完了へ
- focusBlock を正の safe integer のみ許可する parse に変更。regex を /^[1-9]\d*$/ + Number.isSafeInteger + >0 の三重チェックへ。0 が 通ると useBlock(0) が SWR falsy key で fetch を止め ref も query も更新されずスタックしていた - consumedKeyRef のセットを async の uncancelled 完了後に移動。 StrictMode dev double-fire で最初の async が cancel された時に scroll が発火しなくなる問題を解消 - rawFocusBlock === null (query 掃除後) 時点で ref をリセット。同一 block の再通知が同一マウント中に来ても再処理できるようにする - 回帰テスト 2 種追加 (focusBlock=0 / 42 → clearParam → 42 再処理)
useBlock(id) 単発 fetch → selectedPageId 切替の後、対象 page の useBlocks(pageId) list fetch が終わるまで ViewTripLayout は Skeleton を 返し、data-block-id DOM がまだ存在しない。Cloud Run コールドスタート等で list fetch が 3 秒を超えると rAF ポーリングが空振りして scroll が 発火しないケースがあった。 - rAF ポーリングを MutationObserver に変更。Skeleton が Timeline に 差し替わった瞬間に即発火するようになり無駄回転をなくす - timeout を 3s → 8s に延長 (staging 実測でも 3s 超えるケースを確認) - 要素発見後に 1 rAF 挟んでから scrollIntoView。Timeline 差し替え直後は layout がまだ確定していないタイミングで scroll してもズレることがあった
現状は SW → client の postMessage 1 本に依存しており、以下 2 パターンで navigate が発火せず PWA が前面化したまま URL が変わらない事故になる: - Android で PWA が freeze/kill され、matchAll は phantom client を 返すが event loop で message が drain されない - cold start で client 側 message listener 登録より前に SW が postMessage を送出 Cache Storage を safety net にして両方救う。 - SW: 通知タップの target URL を Cache に put してから postMessage/ openWindow へ進む - Client: useFcmNavigationListener を mount + visibilitychange で Cache 内 intent を消化する経路に拡張。fast path (message) と safety net (cache) の二重発火を lastNavigatedRef で dedupe - 現在 URL と一致するときは navigate せず、履歴汚染を避ける - Cache API 非対応環境 / QuotaExceeded は best effort として無視
Android PWA は console を remote inspect 経由でしか見られず、実機再現が 手間なので SW と client の checkpoint を IndexedDB に書き溜めて、URL に ?debug=1 で右下に出るボタンで一括 Copy できるようにする。運用機能では なく本 issue の切り分け作業後に整理する前提。 - lib/debugLogger.ts: 500 件 ring buffer、readAll でフォーマット済み文字列 - firebase-messaging-sw.js: 同じ DB / store 名で inline 実装 (compat SDK で importScripts 経由なので TS 版を共有できない) - components/DebugLogPanel.tsx: 右下 floating。?debug=1 で localStorage 永続化、?debug=0 で解除。clipboard 拒否時は prompt にフォールバック - SW の notificationclick 全経路 / client の message + intent 消化 / useFocusBlockOnMount の effect + wait 完了に checkpoint 挿入
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Visit the preview URL for this PR (updated for commit 8260594): https://tabi-share-8ef6b--pr216-chore-issue207-debug-o6lfwzfh.web.app (expires Sat, 08 Aug 2026 12:29:48 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 9f2a87ede127df7673322845e34cf22c1372d720 |
PWA install 後は URL バーが隠れて ?debug=1 を後付けしにくいので、この 診断専用ブランチでは gate を撤廃し install 直後から Copy Logs / Clear が使える状態にする。PR #216 は DO NOT MERGE 前提なので prod 影響なし。
SW 更新は非同期でユーザ操作依存 (skipWaiting していない) なので、実機で 古い SW が動いてるケースを共有ログから区別できるようにする。 - lib/debugLogger.ts (client): DEBUG_LOG_VERSION 定数を追加、LogEntry に version フィールドを追加、formatEntry で各行に埋め込み - firebase-messaging-sw.js: 対の DEBUG_LOG_VERSION を持ち、SW script evaluate 時に 'sw script evaluated' ログ、各 entry に version 記録 - useFcmNavigationListener 'listener mounted' に SW controller の scriptURL と client version を含める → SW/client のバージョンずれ検知 - DebugLogPanel の Copy 時に client version / SW controller / URL / copied at のヘッダを先頭に付ける 診断修正のたびに 2 定数を手動 bump する運用 (現在 v02)。
実機ログで SW ハンドラが全く発火せず Firebase SDK デフォルト click 処理 だけが動く現象が発生していたが、原因は VitePWA が firebase-messaging-sw.js を precache していたことにあった。register 時に Chrome が cache 経由で 古い版を fetch → 古い SW が active → いくら deploy しても新版に切り替 わらない。 - vite.config.ts の workbox に globIgnores + navigateFallbackDenylist で firebase-messaging-sw.js を除外 - firebase.json で /firebase-messaging-sw.js に Cache-Control: no-cache ヘッダを追加。Firebase Hosting のデフォルト max-age=3600 も抑える - 診断: DEBUG_LOG_VERSION を v03 に bump、client 側で getRegistrations() を呼び出して全 SW の scope / active / waiting / installing の scriptURL をログ出力 feature branch (#209) に同等の修正を反映する前に、まず diagnostic branch で実機挙動確認する。
実機ログで判明: Firebase Admin SDK で WebpushFCMOptions(link=...) を指定 しても、client 側の SW に届く payload では data.FCM_MSG.fcmOptions.link ではなく data.FCM_MSG.notification.click_action に URL が入っていた (Firebase JS SDK 12.16.0)。 現状は fcmOptions.link のみ見ていたので link が常に null になり、SW handler が発火しても deep link 抽出で早期 return していた。実 payload に合わせて click_action を primary、fcmOptions.link と data.link を fallback にする。 DEBUG_LOG_VERSION を v04 に bump。
v04 のログで判明: staging に v04 の SW file が deploy 済みなのに実機は v02 SW が active のまま。Chrome の SW default lifecycle だと新版は waiting → 既存 client 閉じるまで activate 待ち、で反映されない。 - install イベントで self.skipWaiting() - activate イベントで self.clients.claim() で既存 client も掌握 - 各イベントに diagnostic ログを付けて実機で切替タイミングを可視化 - DEBUG_LOG_VERSION を v05 に bump
v05 push 後も実機は v02 SW が active のまま。原因は FCM SW が root scope
に居ないため navigation で update check されず、register() も起動 1 回
のみで、24h の定期 check を待つ状態になっていた (実機で観測)。
- useFcmNavigationListener mount 時に FCM SW の registration に対して
reg.update() を明示的に呼ぶ。Chrome に byte-diff check を促す
- updatefound / statechange イベントを listen して、新 SW が installed →
waiting になった時に SKIP_WAITING message を送る (二重の保険)
- 既に waiting 状態の SW があれば即 SKIP_WAITING を送信
- firebase-messaging-sw.js に message listener を追加。
{ type: 'SKIP_WAITING' } を受けたら self.skipWaiting() を発火
- DEBUG_LOG_VERSION を v06 に bump
これで client mount 時に FCM SW の状態が可視化され、waiting なら強制
activate 経路が動く。v02 SW を追い出す最終手段。
v06 ログで判明: navigateOnce の lastNavigatedRef が unmount まで保持
されるため「一度 navigate した URL には二度と navigate できない」バグが
あった。前セッションで /trip/A?focusBlock=X に遷移後、別 trip に移動して
再度 focusBlock=X の通知タップ → 両経路 (message / intent) とも
'skip (== last)' で navigate 発火せず。
- ref の型を { url, at: performance.now() } に変更
- dedupe 判定を「同 URL かつ 1 秒以内」に限定 (NAV_DEDUPE_WINDOW_MS)
- 1 通知タップに対する message + intent の近接発火は dedupe できる
- それを超えた同 URL 再遷移は許可される
- DEBUG_LOG_VERSION を v07 に bump
Related: #207, #209
目的
PR #209 の実装で「PWA background + 別 trip の通知タップで trip が切り替わらない」ケースが観測された。console が実機 (Android PWA / iOS PWA) から取りづらいため、IndexedDB 経由でログ収集して client の Copy ボタンで貼り付けできる診断ロガーをこのブランチに切って作業する。
このブランチはマージしない。切り分けが終わったら delete。
ベース
feature/issue207_sw-notificationclick-deeplink(#209) + 診断コミット 1 本 (`71cb265`)。使い方
解除: `?debug=0`
checkpoint
期待パターン / 故障パターン
正常 (別 trip 通知タップ):
```
SW notificationclick → SW link extracted → SW intent stored
→ SW matchAll (client あり) → SW target picked → SW postMessage sent → SW focus ok
→ CL sw message → CL navigate() called (source: message)
```
postMessage 未着 (PWA freeze 疑い):
```
SW ... postMessage sent → SW focus ok
→ CL sw message 出ない
→ CL visibilitychange (visible)
→ CL applyIntent → CL navigate() called (source: intent) ← Cache safety net で救えるはず
```
両経路とも効かない場合:
```
SW ... postMessage sent
→ CL 側にログが一切出ない or navigate skip が出続ける
```
原因層が特定できたら、正しい fix を #209 に別コミットで反映してこの Draft PR は close。