Files
jrshikoku/docs/refactor-audit/05-data-network-state.md
harukin-expo-dev-env 68f66d0109 feat: Add CI/CD release audit documentation and refactor roadmap
- Introduced comprehensive CI/CD release audit documentation detailing current repository automation, EAS configuration, manual workflows, and recommended PR pipelines.
- Added a structured refactor roadmap outlining phases for improving API boundaries, domain model integration, and CI/CD automation.
- Proposed specific skills for project management to enhance understanding of application-specific knowledge and maintainability.
- Updated package.json to include expo-sqlite dependency and modified yarn.lock accordingly.
2026-08-29 22:43:44 +09:00

10 KiB

Data / Network / State監査

1. 通信全体像

現状はTanStack Query、React Query、Axios、Redux、Jotai、SWRの導入は確認できない。通信はraw fetch、独自observedFetch、WebView内のbrowser fetch/XHR、Widget/Native Kotlin・Swiftのfetchに分かれている。

Endpoint分類

分類 主な用途 主な経路 状態/観測
JR四国公式Web 走行位置、運行情報、PDF/公式ページ WebView、XHR injection、外部URL WebView lifecycleは観測、ページ内fetchは限定的
R2/Cloudflare系 今日のダイヤ、運行情報、位置JSON、ニュース、Unyohub/Elesite API_ENDPOINTS + provider 一部observedFetch、一部raw fetch
haruk.in Backend /train-data/operation-logs、カスタム列車、Voicepeak provider/client 主にobservedFetch、一部endpoint直書き
GAS バス・列車、位置fallback、補助情報 provider、WebView script raw fetchとfallbackが混在
n8n webhook 駅一覧、位置問題、通知登録、位置変換、画像 component/provider/WebView raw fetch。HTTP/timeout/契約が経路ごとに違う
mock backend 開発・録画・検証 lib/mockApi、XHR interceptor mock専用cache、signal接続に不足あり
Local storage last-good、設定、録画、ダイヤcache AsyncStorage + react-native-storage TTL中央管理なし
Native background Android live notification Kotlin URLSession相当の独自fetch RN観測層を迂回

定数の中心はconstants/api.tsだが、WebView script、Native module、UI componentにもendpointが残る。lib/jrDataSystemEnvironment.tsはproduction/experimental等のBackend環境をまとめる良い境界なので、全callerが使うようにする価値が高い。

2. 共通observedFetchの評価

lib/observability/network/observedFetch.tsは以下を提供する。

  • AbortControllerベースのtimeout
  • HTTP statusのエラー化
  • content-type/JSONらしさの確認
  • response headの短縮
  • timeout/network errorの一回retry
  • Sentry breadcrumb、context、span、slow success
  • app stateとオンライン判定によるbackground/offline skip

ただし次の制約がある。

  • schema/runtime validationはcaller依存で、共通client自体は型を保証しない。
  • nativeではnavigator.onLineが使えず、online判定が実質true fallbackになり得る。
  • 外部Abort signalのlistenerを後始末していない。
  • callerの外部Abortもtimeoutとして扱いretryする経路があり、ユーザーキャンセルと通信障害が混同される。
  • retry待機のsleepがAbort非対応。
  • raw fetchは引き続き観測を迂回する。

3. raw fetchの代表的な問題

箇所 現状 影響
stateBox/useTrainDelayData.tsx text fetch、response.ok/timeout/parse schemaなし 失敗時の原因が分からず、effect依存で初回再実行
stateBox/useUnyohub.tsx / useElesite.tsx JSON直parse、10分interval stale/HTTP error/型不正を区別できない
stateBox/useTrainMenu.tsx permission APIへPush Tokenをuser_id queryとして送信 proxy/server/access logへの識別子露出、tokenと権限identityの結合
components/Settings/NotificationSettings.tsx POST後にresponse.okを見ず成功alert。説明文タップでtokenをClipboardへコピー 登録失敗でも成功表示、意図しないtokenコピー
GeneralWebView.tsx credential付きpage fetch、raw import fetch cookie/URL境界と観測が複雑
Android Widget / Native service RNとは独立したfetch 同じデータの二重取得、原因追跡が分断
WebView injected script 複数endpointをbrowser fetch、finallyで再予約 RN lifecycle外のpolling、cancel/HTTP/errorが別実装

4. Polling / timer matrix

データ 代表的な周期 所有者 background挙動 所見
current positions 15秒(定数/Provider)、WebView側も更新 useCurrentTrain、公式WebView、Native service、Widget 経路ごとに異なる in-flight/世代管理不足、重複取得
timetable / train data / logs 30秒 useAllTrainDiagram、WebView script 一部keepAlive=true、fetch側はbackground拒否し得る timerと通信ポリシー不一致
delay effectの初回/状態変化 useTrainDelayData 画面/Provider寿命 loadingDelayData依存で再実行
Unyohub / Elesite 10分 各hook AppState pauseなし hook毎に独立snapshot/interval
user location 5秒 useUserPosition useIntervalがbackground停止 Provider常時mount、権限・stale closure注意
operation WebView 1秒 blink/install、28ms auto-scroll等 injected JS WebViewページ状態依存 多数のtimer、DOMとの結合
watchdog 5秒 useWebViewRemount AppStateで調整 cleanupはあるが遅延timerに残りあり
navigation retry 250/300ms App/Notification readyまで最大回数 cancel/priorityが暗黙

setIntervalsetTimeoutrequestAnimationFrameAppStateは全体に存在する。React hook側は多くがcleanupを持つが、初期化・再試行・WebView内の再予約では画面unmountとの関係を一つのschedulerで管理していない。

5. 現在位置のrace / duplicate

useCurrentTrainは初期mount用effectとmockApiFeatureEnabled依存effectの両方から取得が走る。15秒intervalにもin-flight制御がなく、R2取得後にGAS fallbackが遅れて返ると新しい値を古い値が上書きし得る。mock経路のAbortControllerは作成されるがfetchMockTrainPositionsへsignalが渡されず、timeoutが実際の通信を停止しない。

最初の改善は、APIを変えずに次の4点である。

  1. fetch中なら次周期をskipする、または一つの共有promiseを返す。
  2. request sequenceを付け、最新sequenceだけstateへ反映する。
  3. fallbackは「primary失敗時のみ」「primaryより古いfetchedAtは反映しない」とする。
  4. last-goodには取得時刻とTTLを付け、stale表示をUIに示す。

6. State分類

種類 現在の例 現在の保持 推奨ルール
Server state 現在位置、ダイヤ、運行情報、Unyohub/Elesite Context state + raw cache + WebView/Widget source/clientが所有。fetchedAt/status/dataを一組で保持
UI state sheet、選択駅、fixed position、orientation gate local state / Context 画面またはfeature内。server dataをコピーしない
Persistent user state start page、favorite、theme、data source、recording AsyncStorage wrapper typed key、schema version、migration、TTL要否を明示
Sensitive state / identifier Push Token、Voicepeak token、Sentry credential AsyncStorage/env/query/clipboard bearer secretはSecureStore/secret manager。Push Tokenはopaque user IDと分離し、URL/logへ出さず、Clipboardは警告付き明示操作だけ
Derived state 最寄り駅、delay label、train position、badge render/effect/helper source stateから純粋計算。別Contextへ保存しない

TanStack Query等がないことは即時の欠陥ではない。先にserver stateの契約とownerを定め、共有fetch/cache/dedupが必要になった時点で、Query導入の対象を限定して再評価する。

Push TokenはSentry auth secretと同一分類ではないが、通知先かつ現行Backendの権限判定identityとして使われるため保護対象である。URL query、development logger、隠れたClipboard gestureをやめ、Backend側のopaque user identity・token rotation・HTTP/schema errorを一つの登録契約にする。

7. Context / Storageの多重管理

buildProvidersTreeで10以上のProviderを常時ネストしている。Provider valueは毎render新しいobject/functionを作るものがあり、購読者の不要rerenderを誘発する。データ取得結果はContext、AsyncStorage、WebViewのscript変数、Native Widgetの別cacheへ複製される。

特にUnyohub/Elesiteは専用hook、TrainMenu、設定画面、WebView scriptで設定・データ参照が分散する。AsyncStorageのreact-native-storage wrapperはcache enabled・expiryなしで、古いデータを現行として返す可能性がある。

8. API改善案(変更順)

Phase A: 入口の契約

request(endpoint, params)
  → timeout / Abort / HTTP
  → parseJson / parseText
  → runtime schema guard
  → { data, source, fetchedAt, receivedAt, schemaVersion }

外部APIごとにclientを作るが、画面からURLを直接渡さない。endpointは識別子、URLは環境設定から解決する。

Phase B: データごとのowner

  • 現在位置: currentPositionRepository 一つ。RN表示と追従機能は同じsnapshotを読む。
  • ダイヤ: timetableRepository 一つ。station diagram/列車詳細は同じ正規化データを読む。
  • 運行情報: 公式WebView表示とRN badgeの役割を分け、同一の更新時刻・エラー表示を共有する。
  • 外部運用: opt-in時だけ起動し、1つのcacheとpollerを共有する。

Phase C: cache policy

endpointごとにmaxAgeallowStalepollIntervalbackgroundretryを宣言する。Storageへ保存する場合は{payload, fetchedAt, expiresAt, source}を保存し、無期限fallbackをやめる。

9. 受け入れ条件

  • 同一endpointの同時呼び出しが1リクエストになる。
  • 遅い応答は新しいsnapshotを上書きしない。
  • HTTP 4xx/5xx、timeout、abort、parse、schema mismatchを別分類できる。
  • 画面をunmountしてもlistener/timer/fetch完了処理がstate更新しない。
  • offline/foreground/backgroundの扱いがendpointごとに明示される。
  • 「いつ」「どのsource」「どのschema」のデータを表示したかをSentryとUIで追跡できる。