- 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.
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が暗黙 |
setInterval、setTimeout、requestAnimationFrame、AppStateは全体に存在する。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点である。
- fetch中なら次周期をskipする、または一つの共有promiseを返す。
- request sequenceを付け、最新sequenceだけstateへ反映する。
- fallbackは「primary失敗時のみ」「primaryより古いfetchedAtは反映しない」とする。
- 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ごとにmaxAge、allowStale、pollInterval、background、retryを宣言する。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で追跡できる。